Last modified December 16, 2024

Creating environments for GitOps resources

On many occasions, you need to set good defaults for your workload clusters but have the ability to change some values depending on the type of environment: development, staging or production for example. In such a case, you can rely on specific bases defined on your GitOps repository. Let’s see in this document how you can create a /bases/environments folder structure.

General note

It’s possible to solve the environment propagation problem in multiple ways, notably by:

  • Using a multi-directory structure, where each environment is represented as a directory in a main branch of a single repository.
  • Using a multi-branch approach, where each branch corresponds to one environment, but they’re in the same repository.
  • Using a multi-repository setup, where there’s one root repository providing all the necessary templates, and then there is another repository per environment.

Each of these approaches has pros and cons. Giant Swarm relies on the multi-directory approach for multiple reasons. It’s a single repository and single branch which serves as the source of truth for all the environments. Easy for template sharing and a relatively simple way to compare and promote configuration across environments. On the other hand, it needs extra work for access control management, template versioning or environment drift detection.

Environment types

There are many ways to separate your cluster configuration but based in our experience there are two main factors: stages and regions. You can always adapt further this to your own requirements following the same principles described below.

Lets start creating a new folder in our bases root directory. You will call it environmentsm then you can add two new folders stages and regions.

The stages folder groups environment specifications. You can have a variety of clusters - like the development, staging and production - based on the same template but with different values.

The regions folder groups specifications for configurations referred to different locations or regions in the different cloud providers or data centers.

In order to avoid possible collisions in the configuration, you can define different changes in the configuration across several environment folders. For example:

  • stages should refer to versions of the cluster and apps running, and different configurations associated to the stage.
  • regions should contain configurations referred to the locality, like networking or DNS zones.

Let’s assume all the clusters following this environment’s pattern should look similar across all the environments. Even so, each layer introduces some key differences, like the app version being deployed for dev/staging/prod environments or a specific IP range.

In order to create a new environment template, you need to make a directory in environments that describes the best differentiating factor for that kind of environment. Then, you create a child folder with different values inside.

For example, in the case of multiple regions, the recommendation is to put region-specific configurations into /bases/environments/regions directory (likeeu_central or us_west).

Once your environment templates are ready, you can create new clusters by placing the definitions in /management-clusters/MC_NAME/organizations/ORG_NAME/workload-clusters and referencing the template as shown in the example below.

Stages

Now, let’s create two cluster stage templates under /bases/environments/stages.

mkdir -p bases/environments/stages/dev
mkdir -p bases/environments/stages/prod

For each stage, you create a kustomization.yaml file. This file has a reference to the base template. The goal is just to overwrite or add the values specific to this stage.

Let’s see how to create these files for the different environments.

The development cluster

In the development cluster kustomization.yaml file you find three main sections. First, the block generates the ConfigMaps with the development configuration values. The second one is the patch structure adding a reference to these ConfigMaps in the cluster app customer resource. Finally the reference to the base template where defaults for a cluster live.

apiVersion: kustomize.config.k8s.io/v1beta1
buildMetadata: [originAnnotations]
configMapGenerator:
  - files:
    - values=cluster_config.yaml
    name: ${cluster_name}-dev-config
    namespace: org-${organization}
  - files:
    - values=default_apps_config.yaml
    name: ${cluster_name}-default-apps-dev-config
    namespace: org-${organization}
generatorOptions:
  disableNameSuffixHash: true
kind: Kustomization
patches:
  - patch: |
      - type: merge
        op: add
        path: /spec/extraConfigs/0
        value:
          name: ${cluster_name}-dev-config
          namespace: org-${organization}
          priority: 110      
    target:
      group: application.giantswarm.io
      kind: App
      name: ${cluster_name}
      namespace: org-\${organization}
  - patch: |
      - type: merge
        op: add
        path: /spec/extraConfigs/0
        value:
          name: ${cluster_name}-default-apps-dev-config
          namespace: org-${organization}
          priority: 110      
    target:
      group: application.giantswarm.io
      kind: App
      name: ${cluster_name}-defaul-apps
      namespace: org-\${organization}
resources:
  - ../../../../../../../../bases/clusters/capa/template/

Next to the kustomization, you create the development configuration for the cluster. Use cluster_config.yaml as name. It only contains the values specific for this stage:

controlPlane:
  replicas: 1
machinePools:
- instanceType: m5.large
  maxSize: 5
  minSize: 3
  name: machine-dev-pool0
  rootVolumeSizeGB: 100
network:
  availabilityZoneUsageLimit: 1

Now you do the same for the defaults app configuration creating the file default_apps_config.yaml:

userConfig:
  apps:
    cilium:
      version: 0.6.1
    coreDNS:
      version: 1.13.0

The final structure of this folder would be:

bases/environments/stages/dev
└── cluster_config.yaml
└── default_apps_config.yaml
└── kustomization.yaml

You are done with the development cluster. Let’s have a look at how to define a production template with some minor differences.

The production cluster

You will create the same structure as the development one but with some different values for the cluster and default apps:

apiVersion: kustomize.config.k8s.io/v1beta1
buildMetadata: [originAnnotations]
configMapGenerator:
  - files:
    - values=cluster_config.yaml
    name: ${cluster_name}-prod-config
    namespace: org-${organization}
generatorOptions:
  disableNameSuffixHash: true
kind: Kustomization
patches:
  - patch: |
      - type: merge
        op: add
        path: /spec/extraConfigs/0
        value:
          name: ${cluster_name}-prod-config
          namespace: org-${organization}
          priority: 110      
    target:
      group: application.giantswarm.io
      kind: App
      name: ${cluster_name}
      namespace: org-\${organization}
resources:
  - ../../../../../../../../bases/clusters/capa/v0.21.0/

Warning: For the sake of simplicity both reference the same base template, but in some occasions where there are breaking changes you might need to link a different template. Read more about bases here.

Now, you define the production configuration in a new file cluster_config.yaml. You can configure this time a completely different value set, even override new configurations from default.

controlPlane:
  replicas: 3
machinePools:
- instanceType: m5.2xlarge
  maxSize: 10
  minSize: 5
  name: machine-prod-pool0
  rootVolumeSizeGB: 300
network:
  availabilityZoneUsageLimit: 3

The final structure of our stages is:

bases/environments/stages/
├── dev
│       ├── cluster_config.yaml
│       └── kustomization.yaml
└── prod
        ├── cluster_config.yaml
        └── kustomization.yaml

Note: In a similar way, you could base your environments on different cluster templates from bases. You can create as many levels as you want, but take into account always to set the right priorities of the configuration files.

Regions

Following the same structure as stage environments, you can create a new folder regions under the environments bases directory.

mkdir -p bases/environments/regions/eu_central
mkdir -p bases/environments/regions/us_west

cd bases/environments/regions

Let’s define a eu-central region where you describe some specifics related to the zone. For that, you create a kustomization.yaml file and cluster_config.yaml file same as you did in previous step.

apiVersion: kustomize.config.k8s.io/v1beta1
buildMetadata: [originAnnotations]
configMapGenerator:
  - files:
    - values=cluster_config.yaml
    name: ${cluster_name}-region-config
    namespace: org-${organization}
generatorOptions:
  disableNameSuffixHash: true
patches:
  - patch: |
      - op: add
        path: /spec/extraConfigs/0
        value:
          - name: "${cluster_name}-region-config"
            namespace: org-${organization}
            priority: 120      
    target:
      group: application.giantswarm.io
      kind: App
      name: ${cluster_name}
      namespace: org-${organization}
kind: Kustomization
controlPlane:
  availabilityZones:
    - eu-central-1
    - eu-central-2
    - eu-central-3
nodeCIDR: "10.32.0.0/24"

Note: These values are examples and need to be replaced by real values of the user account.

The Kustomize plugin in Flux will create the new ConfigMap with the region values. Note that priority is set to 120 which will precede over the stage values.

In case you have other region, for example us-west, create a cluster_config.yaml file with the different configuration.

controlPlane:
  availabilityZones:
    - us-west-1
    - us-west-2
nodeCIDR: "10.64.0.0/24"

The folder structure resulting from this is:

bases/environments/regions
├── eu-central
│   ├── cluster_config.yaml
│   └── kustomization.yaml
└── us_west
    ├── cluster_config.yaml
    └── kustomization.yaml

Use environment templates

After having learnt how to create your own environment templates, lets create a cluster based one those. First, you need to get the cluster App resource running this command:

kubectl gs gitops add workload-cluster \
--management-cluster $MC_NAME \
--name $WC_NAME \
--organization $ORG_NAME \
--repository-name ${MC_NAME}-git-repository \
--base bases/environments/stages/dev \
--release 0.21.0

In this case, you decide to use the development stage template. Read more about creating workload clusters. In this example, you just need to tweak the kustomization.yaml on /management-clusters/MC_NAME/organizations/ORG_NAME/workload-clusters/WC_NAME/mapi/cluster/ to include region and stage template references:

apiVersion: kustomize.config.k8s.io/v1beta1
commonLabels:
  giantswarm.io/managed-by: flux
kind: Kustomization
resources:
  - ../../../../../../../../bases/environments/stages/dev
# Include a new line with the resources that provide region configuration
  - ../../../../../../../../bases/environments/regions/eu-central

Note: It uses the extra configuration feature of App resource to patch additional layers of configurations. Read more here.

Tips for developing environments

For complex clusters, you can end up merging a lot of layers of templates and configurations. In order to keep sanity, it’s very important to establish a good priority system and respect it every time. As an example, reserve priority values for different levels:

  • 100 for cluster base
  • 110 for stage environment
  • 120 for the regional environment
  • 130 for specific cluster configuration

Look at our gitops-template repository which contains several examples of cluster bases and configuration layers.

Further, you can find tooling to verify your resources and ensure that the configuration is correct.

This part of our documentation refers to our vintage product. The content may be not valid anymore for our current product. Please check our new documentation hub for the latest state of our docs.