Skip to main content

Multi-environments


Table of Contents

  1. Management strategies
  2. Promotion between environments
  3. Kustomize for multi-env
  4. Helm for multi-env
  5. ApplicationSet (ArgoCD)
  6. Hands-on exercises

1 - Management strategies

Typical environments

Strategy 1: Branch per environment

main         → Production
staging → Staging
develop → Development
# ArgoCD - Application par branche
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
spec:
source:
targetRevision: main # Branche
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-staging
spec:
source:
targetRevision: staging

Strategy 2: Directory per environment

gitops-repo/
├── apps/myapp/
│ ├── base/
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── prod/
# Applications ciblant différents dossiers
spec:
source:
path: apps/myapp/overlays/prod

Strategy 3: Repo per environment

org/gitops-dev
org/gitops-staging
org/gitops-prod

Comparison

StrategyProsCons
BranchSimple, PR for promotionMerge conflicts
DirectoryAtomic commits, unified viewDuplication
RepoTotal isolation, permissionsFragmentation

🔝 Back to table of contents


2 - Promotion between environments

Promotion workflow

Manual promotion (PR)

# 1. Tester en staging
git checkout staging
git merge develop
git push

# 2. Vérifier le déploiement staging

# 3. Promouvoir en production
git checkout main
git merge staging
git push

Automatic promotion (Image Automation)

# Flux Image Automation
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
name: myapp
spec:
imageRepositoryRef:
name: myapp
policy:
semver:
range: 1.x.x

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: myapp
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: [email protected]
name: Flux
messageTemplate: 'Update {{.AutomationObject.Name}}'
update:
path: ./apps/myapp

Gates and Approvals

# ArgoCD Sync Waves
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # Ordre de sync
# Flux - Dépendance entre Kustomizations
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
spec:
dependsOn:
- name: infrastructure # Déployer infra d'abord

🔝 Back to table of contents


3 - Kustomize for multi-env

Structure

apps/myapp/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ └── replicas-patch.yaml
├── staging/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ └── replicas-patch.yaml
└── prod/
├── kustomization.yaml
├── namespace.yaml
├── replicas-patch.yaml
├── resources-patch.yaml
└── hpa.yaml

Common base

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml
- configmap.yaml

commonLabels:
app: myapp
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
template:
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: LOG_LEVEL
value: info
resources:
requests:
memory: "64Mi"
cpu: "50m"

Dev Overlay

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: dev

resources:
- ../../base
- namespace.yaml

patches:
- path: replicas-patch.yaml

images:
- name: myapp
newTag: dev-latest

configMapGenerator:
- name: myapp-config
behavior: merge
literals:
- LOG_LEVEL=debug

Prod Overlay

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

resources:
- ../../base
- namespace.yaml
- hpa.yaml

patches:
- path: replicas-patch.yaml
- path: resources-patch.yaml

images:
- name: myapp
newTag: v1.2.3

configMapGenerator:
- name: myapp-config
behavior: merge
literals:
- LOG_LEVEL=warn
# overlays/prod/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5

# overlays/prod/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"

🔝 Back to table of contents


4 - Helm for multi-env

Values files structure

apps/myapp/
├── Chart.yaml
├── values.yaml # Base
├── values-dev.yaml
├── values-staging.yaml
└── values-prod.yaml

values.yaml (base)

replicaCount: 1

image:
repository: myapp
tag: latest

resources:
requests:
memory: 64Mi
cpu: 50m

config:
logLevel: info

values-prod.yaml

replicaCount: 5

image:
tag: v1.2.3

resources:
requests:
memory: 512Mi
cpu: 500m
limits:
memory: 1Gi
cpu: 1

config:
logLevel: warn

autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10

ArgoCD with Helm

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
spec:
source:
repoURL: https://github.com/org/gitops-repo
path: apps/myapp
helm:
valueFiles:
- values.yaml
- values-prod.yaml # Override

Flux with Helm

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: myapp
namespace: production
spec:
chart:
spec:
chart: ./apps/myapp
sourceRef:
kind: GitRepository
name: gitops-repo
values:
replicaCount: 5
image:
tag: v1.2.3

🔝 Back to table of contents


5 - ApplicationSet (ArgoCD)

Concept

ApplicationSet automatically generates ArgoCD Applications.

List Generator

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp
spec:
generators:
- list:
elements:
- env: dev
namespace: dev
replicas: "1"
- env: staging
namespace: staging
replicas: "2"
- env: prod
namespace: production
replicas: "5"
template:
metadata:
name: 'myapp-{{env}}'
spec:
project: default
source:
repoURL: https://github.com/org/gitops-repo
path: 'apps/myapp/overlays/{{env}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true

Git Generator

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-envs
spec:
generators:
- git:
repoURL: https://github.com/org/gitops-repo
revision: HEAD
directories:
- path: apps/myapp/overlays/*
template:
metadata:
name: 'myapp-{{path.basename}}'
spec:
source:
path: '{{path}}'
destination:
namespace: '{{path.basename}}'

Cluster Generator (Multi-cluster)

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-clusters
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: 'myapp-{{name}}'
spec:
destination:
server: '{{server}}'
namespace: production

🔝 Back to table of contents


6 - Hands-on exercises

Quiz

Q1. Which strategy avoids merge conflicts?

Answer

Directory per environment because all environments are on the same branch (main). Changes are isolated in different folders.

Q2. How to deploy the same app to 3 clusters with ArgoCD?

Answer

Use an ApplicationSet with a Cluster Generator:

generators:
- clusters:
selector:
matchLabels:
env: production

Exercise: Multi-env configuration

Create the Kustomize overlays for dev and prod with these differences:

AspectDevProd
Replicas15
Memory128Mi1Gi
Log leveldebugwarn
Solution
# overlays/dev/kustomization.yaml
namespace: dev
resources:
- ../../base
configMapGenerator:
- name: config
literals:
- LOG_LEVEL=debug

# overlays/prod/kustomization.yaml
namespace: prod
resources:
- ../../base
patches:
- path: deployment-patch.yaml
configMapGenerator:
- name: config
literals:
- LOG_LEVEL=warn

# overlays/prod/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
template:
spec:
containers:
- name: myapp
resources:
requests:
memory: 1Gi

🔝 Back to table of contents


Key takeaways

  • 3 strategies: Branch, Directory, Repo per env
  • Directory avoids merge conflicts
  • Promotion via PR or automatic (Image Automation)
  • Kustomize: base + overlays
  • Helm: values files per environment
  • ApplicationSet: automatic multi-env generation

🔝 Back to table of contents


← Previous chapter | Next chapter: Best practices →