GitOps Deployment with Argo CD and Kustomize
GitOps makes Git the desired-state record for Kubernetes: instead of an engineer applying manifests manually with kubectl, a controller continuously compares what is declared in Git with what exists in the cluster and reconciles differences. Argo CD performs that reconciliation; Kustomize renders reusable Kubernetes configuration for different environments without copying the entire manifest set.argo-cd.readthedocs+1.
For a practical platform setup, keep application source code separate from deployment configuration. Your CI pipeline builds, scans, and publishes an immutable container image; it then updates an image tag or digest in the GitOps repository through a reviewed pull request. Argo CD sees the approved Git change and deploys it.
Kustomize uses a base for shared manifests and overlays for environment-specific changes. An overlay points to the base and applies only the patches or additions needed for an environment.
platform-gitops/
├── apps/
│ └── inventory-api/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── kustomization.yaml
│ │ └── networkpolicy.yaml
│ └── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── deployment-patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── deployment-patch.yaml
│ └── prod/
│ ├── kustomization.yaml
│ └── deployment-patch.yaml
└── argocd/
├── projects/
└── applications/Keep environment differences narrowly scoped: image reference, replica count, resource requests/limits, ingress hostname, environment configuration, and possibly namespace. Avoid putting credentials in plain YAML; reference secrets managed through External Secrets, Sealed Secrets, SOPS, or another controlled mechanism.
The base defines stable application behavior shared by every environment:
# apps/inventory-api/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
– deployment.yaml
– service.yaml
– networkpolicy.yaml
commonLabels:
app.kubernetes.io/name: inventory-api
app.kubernetes.io/managed-by: argocd
# apps/inventory-api/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inventory-api
spec:
replicas: 2
selector:
matchLabels:
app: inventory-api
template:
metadata:
labels:
app: inventory-api
spec:
securityContext:
runAsNonRoot: true
containers:
- name: api
image: registry.example.internal/inventory-api:0.0.0
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256MiAlways render locally before committing:
kubectl kustomize apps/inventory-api/overlays/dev
kubectl diff -k apps/inventory-api/overlays/dev
The first command shows the generated manifests; the second helps you reason about the live-cluster impact before Argo CD applies it.
Define Environment Overlays
An overlay imports the base, changes its namespace, and patches only what differs. Here is a production overlay:
# apps/inventory-api/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: inventory-prod
resources:
– ../../base
images:
– name: registry.example.internal/inventory-api
newName: registry.example.internal/inventory-api
newTag: “1.7.3”
patches:
– path: deployment-patch.yaml
# apps/inventory-api/overlays/prod/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inventory-api
spec:
replicas: 4
template:
spec:
containers:
- name: api
env:
- name: LOG_LEVEL
value: "info"This separation is the main Kustomize benefit: the base contains universal configuration, while overlays supply only environment deltas. Prefer explicit patches over a growing chain of generators and transformations that make rendered output difficult to review.
An Argo CD Application tells Argo CD which repository path to render and the destination cluster and namespace to reconcile. The Application specification supports Git source details, Kustomize configuration, destination, and sync policy.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: inventory-api-prod
namespace: argocd
spec:
project: production
source:
repoURL: https://git.example.internal/platform-gitops.git
targetRevision: main
path: apps/inventory-api/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: inventory-prod
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
– CreateNamespace=true
– PruneLast=true
retry:
limit: 3
backoff:
duration: 10s
factor: 2
maxDuration: 3m
With automated, Argo CD applies desired-state changes found in Git. selfHeal: true tells it to correct out-of-band cluster changes, while prune: true allows it to remove resources that were deliberately removed from Git. These controls are powerful: in production, protect the Git branch, require review, restrict who can modify Application objects, and use Argo CD Projects to limit the repositories, clusters, namespaces, and resource kinds each team may deploy.
A secure GitOps delivery flow is:
-
A developer commits application code.
-
CI runs unit, integration, image, dependency, and policy checks.
-
CI builds an image, scans it, signs it if your supply chain supports signing, and pushes it using an immutable tag or digest.
-
CI opens a pull request that changes only the relevant overlay image tag or digest.
-
A reviewer verifies the rendered manifest and approves the GitOps change.
-
The merge becomes the desired state; Argo CD synchronizes it to the cluster.
-
Argo CD reports health and sync status; alerts fire on degraded, missing, or out-of-sync workloads.
Avoid allowing CI to run broad kubectl apply commands directly against production. That creates a second deployment authority and makes drift harder to explain. With self-healing enabled, a direct manual scale operation or kubectl edit change is eventually reverted because Git remains authoritative.
GitOps rollback means reverting the commit that introduced the bad desired state, then allowing Argo CD to reconcile the previous revision. Keep image references immutable: a tag like latest undermines auditability because the same Git commit could produce different runtime artifacts.
When an application is OutOfSync or Degraded, troubleshoot in order:
-
Render locally:
kubectl kustomize apps/inventory-api/overlays/prod. -
Inspect the Argo CD diff to find the desired-versus-live mismatch.
-
Check Application events and sync history.
-
Inspect the affected Kubernetes object with kubectl describe.
-
Check Pod status, events, logs, readiness probes, resource quotas, and NetworkPolicies.
-
Confirm the Argo CD service account has only the RBAC permissions it needs.
-
Check whether a controller is legitimately mutating fields; configure ignore differences only for understood and documented cases.
For a production-quality next step, choose one application you would deploy and identify the three fields that should differ between dev and prod while the base stays identical.
[mai mult...]