The Chaos of Configuration Drift
As your application grows from a monolith to a fleet of microservices, managing its configuration becomes exponentially harder. Each service has its own deployments, services, and config maps. Now multiply that by the number of environments: dev, staging, prod. Before you know it, you're drowning in a sea of YAML files.
Manually promoting changes between environments becomes a series of error-prone kubectl apply commands and copy-paste mistakes. staging slowly drifts from prod, and soon nobody trusts the deployment process. This was our reality.
The Solution: A Single Source of Truth
We solved this by adopting a pure GitOps workflow using two powerful tools: Argo CD and Kustomize. The core principle is the App of Apps pattern, where one parent Argo CD Application resource manages a set of child Application resources.
This creates a beautiful, hierarchical structure where everything is defined declaratively in a single Git repository.
Here's what our GitOps repository looks like:
graph TD
subgraph Git Repository
A(root-app.yaml) --> B(apps/);
B --> C(apps/prometheus.yaml);
B --> D(apps/grafana.yaml);
B --> E(apps/my-app.yaml);
F(kustomize/base) --> G(deployment.yaml);
F --> H(service.yaml);
I(kustomize/overlays/staging) --> J(kustomization.yaml);
J -- patches --> G;
J -- sets replicas=1 --> G;
K(kustomize/overlays/production) --> L(kustomization.yaml);
L -- patches --> G;
L -- sets replicas=10 --> G;
end
subgraph Argo CD
M(Root App) -- manages --> N(Child Apps);
N -- syncs --> C & D & E;
end
How It Works
-
The Root App: We deploy a single
Applicationto our cluster, pointing it to a directory in our Git repository that contains the definitions for all our other applications.# root-app.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: root namespace: argocd spec: project: default source: repoURL: 'https://github.com/my-org/cluster-config.git' path: apps/ # This tells Argo to find all Application manifests in the directory directory: recurse: true destination: server: 'https://kubernetes.default.svc' syncPolicy: automated: prune: true selfHeal: true -
Child Apps & Kustomize Overlays: Each child application is defined in its own YAML file. Here, we use Kustomize to manage environment-specific configurations. We have a
baseconfiguration with all the common resources, and thenoverlaysforstagingandproductionthat only define the differences.# apps/my-api-service.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-api-service-staging namespace: argocd spec: source: repoURL: 'https://github.com/my-org/cluster-config.git' # Point to the staging overlay path: kustomize/overlays/staging destination: server: 'https://kubernetes.default.svc' namespace: staging
The Power of Git-Based Promotion
Promoting a change from staging to production is no longer a manual kubectl command. It's a pull request.
To promote a new image version, a developer simply opens a PR to change the image tag in the production Kustomize overlay. This PR is reviewed, approved, and merged. Argo CD automatically detects the change in Git and syncs the new version to the production cluster. The Git history becomes a perfect audit log of every change ever made to production.
The Impact: Confidence and Resilience
- No More Configuration Drift: Git is the single source of truth. The cluster state always matches what's in the main branch.
- Auditable & Reversible Changes: Every change is a Git commit. A bad change is as easy to revert as a
git revert. - Disaster Recovery as Code: If a cluster melts down, we can restore its entire state by simply pointing a new cluster to our Git repository. We've tested it.
By embracing the App of Apps pattern, we turned a chaotic, manual process into a streamlined, automated, and auditable system that gives us the confidence to deploy at scale.