
Gitops Principles
- 46 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Master advanced Git workflows for history management, debugging, and collaboration.
About
Comprehensive GitOps methodology and principles skill for cloud-native operations.
- Different access controls per repo
- Separation of concerns
Gitops Principles by the numbers
- 46 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #311 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill gitops-principlesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 46 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Master advanced Git workflows for history management, debugging, and collaboration.
Files
GitOps Principles Skill
Complete guide for implementing GitOps methodology in Kubernetes environments - the operational framework where Git is the single source of truth for declarative infrastructure and applications.
What is GitOps?
GitOps is a set of practices that uses Git repositories as the source of truth for defining the desired state of infrastructure and applications. An automated process ensures the production environment matches the state described in the repository.
The OpenGitOps Definition (CNCF)
GitOps is defined by four core principles established by the OpenGitOps project (part of CNCF):
| Principle | Description |
|---|---|
| 1. Declarative | The entire system must be described declaratively |
| 2. Versioned and Immutable | Desired state is stored in a way that enforces immutability, versioning, and retention |
| 3. Pulled Automatically | Software agents automatically pull desired state from the source |
| 4. Continuously Reconciled | Agents continuously observe and attempt to apply desired state |
Core Concepts Quick Reference
Git as Single Source of Truth
┌─────────────────────────────────────────────────────────────────┐
│ GIT REPOSITORY │
│ (Single Source of Truth for Desired State) │
├─────────────────────────────────────────────────────────────────┤
│ manifests/ │
│ ├── base/ # Base configurations │
│ │ ├── deployment.yaml │
│ │ ├── service.yaml │
│ │ └── kustomization.yaml │
│ └── overlays/ # Environment-specific │
│ ├── dev/ │
│ ├── staging/ │
│ └── production/ │
└─────────────────────────────────────────────────────────────────┘
│
▼ Pull (not Push)
┌─────────────────────────────────────────────────────────────────┐
│ GITOPS CONTROLLER │
│ (ArgoCD / Flux / Kargo) │
│ - Continuously watches Git repository │
│ - Compares desired state vs actual state │
│ - Reconciles differences automatically │
└─────────────────────────────────────────────────────────────────┘
│
▼ Apply
┌─────────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ (Actual State / Runtime Environment) │
└─────────────────────────────────────────────────────────────────┘Push vs Pull Model
| Push Model (Traditional CI/CD) | Pull Model (GitOps) |
|---|---|
| CI system pushes changes to cluster | Agent pulls changes from Git |
| Requires cluster credentials in CI | Credentials stay within cluster |
| Point-in-time deployment | Continuous reconciliation |
| Drift goes undetected | Drift automatically corrected |
| Manual rollback process | Rollback = git revert |
Key GitOps Benefits
1. Auditability: Git history = deployment history 2. Security: No external access to cluster required 3. Reliability: Automated drift correction 4. Speed: Deploy via PR merge 5. Rollback: Simple git revert 6. Disaster Recovery: Redeploy entire cluster from Git
Repository Strategies
Monorepo vs Polyrepo
Monorepo (Single repository for all environments):
gitops-repo/
├── apps/
│ ├── app-a/
│ │ ├── base/
│ │ └── overlays/
│ │ ├── dev/
│ │ ├── staging/
│ │ └── prod/
│ └── app-b/
└── infrastructure/
├── monitoring/
└── networking/Polyrepo (Separate repositories):
# Repository per concern
app-a-config/ # App A manifests
app-b-config/ # App B manifests
infrastructure/ # Shared infrastructure
cluster-bootstrap/ # Cluster setupMulti-Repository Pattern (This Project)
Separates infrastructure from values for security boundaries:
infra-team/ # Base configurations, ApplicationSets
├── applications/ # ArgoCD Application definitions
└── helm-base-values/ # Default Helm values
argo-cd-helm-values/ # Environment-specific overrides
├── dev/ # Development values
├── stg/ # Staging values
└── prd/ # Production valuesBenefits:
- Different access controls per repo
- Separation of concerns
- Environment-specific secrets isolated
Branching Strategies
Environment Branches
main ────────────────────────────────────► Production
│
└──► staging ──────────────────────────► Staging cluster
│
└──► develop ───────────────────► Development clusterTrunk-Based with Overlays (Recommended)
main ────────────────────────────────────► All environments
│
├── overlays/dev/ → Dev cluster
├── overlays/staging/ → Staging cluster
└── overlays/prod/ → Prod clusterRelease Branches
main
│
├── release/v1.0 ──────► Production (v1.0)
├── release/v1.1 ──────► Production (v1.1)
└── release/v2.0 ──────► Production (v2.0)Sync Policies and Strategies
Automated Sync
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Revert manual changesManual Sync (Production Recommended)
syncPolicy:
automated: null # Require explicit syncSync Options
| Option | Use Case |
|---|---|
CreateNamespace=true | Auto-create missing namespaces |
PruneLast=true | Delete after successful sync |
ServerSideApply=true | Handle large CRDs |
ApplyOutOfSyncOnly=true | Performance optimization |
Replace=true | Force resource replacement |
Declarative Configuration Patterns
Kustomize Pattern
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patchesStrategicMerge:
- replica-patch.yaml
images:
- name: myapp
newTag: v1.2.3Helm Pattern
# Application pointing to Helm chart
spec:
source:
repoURL: https://charts.example.com
chart: my-app
targetRevision: 1.2.3
helm:
releaseName: my-app
valueFiles:
- values.yaml
- values-prod.yamlMulti-Source Pattern
spec:
sources:
- repoURL: https://charts.bitnami.com/bitnami
chart: nginx
targetRevision: 15.0.0
helm:
valueFiles:
- $values/nginx/values-prod.yaml
- repoURL: https://github.com/org/values.git
targetRevision: main
ref: valuesProgressive Delivery Integration
GitOps enables progressive delivery patterns:
Blue-Green Deployments
# Two applications, traffic shift via Ingress/Service
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-blue
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-greenCanary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 10m}Environment Promotion (Kargo)
Warehouse → Dev Stage → Staging Stage → Production Stage
│ │ │ │
└── Freight promotion through environments ───┘Cloud Provider Integration
Azure Arc-enabled Kubernetes & AKS
Azure provides a managed ArgoCD experience through the Microsoft.ArgoCD cluster extension:
# Simple installation (single node)
az k8s-extension create \
--resource-group <rg> --cluster-name <cluster> \
--cluster-type managedClusters \
--name argocd \
--extension-type Microsoft.ArgoCD \
--release-train preview \
--config deployWithHighAvailability=false
# Production with workload identity (recommended)
# Use Bicep template - see references/azure-arc-integration.mdKey Benefits:
| Feature | Description |
|---|---|
| Managed Installation | Azure handles deployment and upgrades |
| Workload Identity | Azure AD authentication without secrets |
| Multi-Cluster | Consistent GitOps across hybrid environments |
| Azure Integration | Native ACR, Key Vault, Azure AD support |
Prerequisites:
- Azure Arc-connected cluster OR MSI-based AKS cluster
Microsoft.KubernetesConfigurationprovider registeredk8s-extensionCLI extension installed
See references/azure-arc-integration.md for complete setup guide.
---
Security Considerations
Secrets Management
Never store secrets in Git! Use:
| Approach | Tool |
|---|---|
| External Secrets | External Secrets Operator |
| Sealed Secrets | Bitnami Sealed Secrets |
| SOPS | Mozilla SOPS encryption |
| Vault | HashiCorp Vault + CSI |
| Cloud KMS | AWS/Azure/GCP Key Management |
RBAC Best Practices
# Limit ArgoCD to specific namespaces
apiVersion: argoproj.io/v1alpha1
kind: AppProject
spec:
destinations:
- namespace: 'team-a-*'
server: https://kubernetes.default.svc
sourceRepos:
- 'https://github.com/org/team-a-*'Network Policies
- GitOps controller should be only component with Git access
- Restrict egress from application namespaces
- Use network policies to isolate environments
Observability and Debugging
Health Status Interpretation
| Status | Meaning | Action |
|---|---|---|
| Healthy | All resources running | None |
| Progressing | Deployment in progress | Wait |
| Degraded | Health check failed | Investigate |
| Suspended | Manually paused | Resume when ready |
| Missing | Resource not found | Check manifests |
Common Issues Checklist
1. Sync Failed: Check YAML syntax, RBAC permissions 2. OutOfSync: Compare diff, check ignoreDifferences 3. Degraded: Check Pod logs, resource limits 4. Missing: Verify namespace, check pruning settings
Drift Detection
# Check application diff
argocd app diff myapp
# Force refresh from Git
argocd app get myapp --refreshQuick Decision Guide
When to Use GitOps
- Kubernetes-native workloads
- Multiple environments (dev/staging/prod)
- Need audit trail for deployments
- Team collaboration on infrastructure
- Disaster recovery requirements
When GitOps May Not Fit
- Rapidly changing development environments
- Legacy systems without declarative configs
- Real-time configuration changes required
- Single developer, single environment
References
For detailed information, see:
references/core-principles.md- Deep dive into the 4 pillarsreferences/patterns-and-practices.md- Branching and repo patternsreferences/tooling-ecosystem.md- ArgoCD vs Flux vs Kargoreferences/anti-patterns.md- Common mistakes to avoidreferences/troubleshooting.md- Debugging guidereferences/azure-arc-integration.md- Azure Arc & AKS GitOps setup
Templates
Ready-to-use templates in templates/:
application.yaml- ArgoCD Application exampleapplicationset.yaml- Multi-cluster deploymentkustomization.yaml- Kustomize overlay structure
Scripts
Utility scripts in scripts/:
gitops-health-check.sh- Validate GitOps setup
External Resources
- OpenGitOps Principles
- ArgoCD Documentation
- Flux Documentation
- Kargo Documentation
- GitOps Working Group
- Azure Arc GitOps with ArgoCD
- Azure Arc-enabled Kubernetes
---
Gotchas
- `selfHeal: true` fights kubectl edit: Engineers patching live resources during incident response will see their changes reverted within seconds. Either pause auto-sync first or commit the fix to Git.
- Monorepo + ApplicationSet git generator scales the controller hard: Every directory change re-renders every app. A 200-app repo with a noisy
mainbranch can pin controller CPU. Split repos or use webhooks instead of polling. - `PruneLast=true` and Helm hooks collide: Hooks run during sync; prune happens after. Resources from post-install hooks get deleted on next sync because they're not in the rendered manifest. Annotate hooks with
Prune=false. - Drift correction depends on the controller noticing: Resources outside ArgoCD's tracked set (e.g. dynamic Secrets from External Secrets Operator) are NOT reconciled — drift in them is invisible. Use explicit
ignoreDifferences. - Sealed Secrets are cluster-scoped: A SealedSecret encrypted for cluster A will not decrypt on cluster B. Multi-cluster GitOps needs per-cluster keys or a shared key (which defeats the security model).
- `git revert` rollback assumes immutable image tags: Reverting a manifest reverts the tag string, but a mutable tag (e.g.
:latest) now points at a different image. Pin to digests for true rollback safety.
GitOps Anti-Patterns
Common mistakes and pitfalls to avoid when implementing GitOps, with guidance on proper practices.
Configuration Anti-Patterns
Anti-Pattern 1: Imperative Commands in Production
The Problem:
# DON'T DO THIS
kubectl scale deployment nginx --replicas=5
kubectl set image deployment/nginx nginx=nginx:1.22
kubectl edit configmap app-configWhy It's Bad:
- Changes are not tracked in Git
- Drift occurs between Git and cluster
- No audit trail
- Changes lost on next sync
The Fix:
# DO THIS: Update Git, let GitOps sync
# manifests/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 5 # Changed from 3
template:
spec:
containers:
- name: nginx
image: nginx:1.22 # Updated version---
Anti-Pattern 2: Mutable Image Tags
The Problem:
# DON'T DO THIS
containers:
- name: app
image: myapp:latest
# or
image: myapp:dev
# or
image: myapp:stableWhy It's Bad:
- Same tag, different content over time
- No way to track what's actually deployed
- Rollback doesn't work (
:latestchanged) - Cache issues across nodes
The Fix:
# DO THIS: Use immutable tags or digests
containers:
- name: app
image: myapp:v1.2.3
# or even better
image: myapp@sha256:abc123def456...Automated Fix with Kargo/Flux:
# Warehouse subscription
subscriptions:
- image:
repoURL: myregistry/myapp
imageSelectionStrategy: SemVer
constraint: ^1.0.0---
Anti-Pattern 3: Secrets in Git
The Problem:
# DON'T DO THIS - NEVER COMMIT SECRETS
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: bXlzZWNyZXRwYXNzd29yZA== # base64 != encryption!Why It's Bad:
- Git history is forever
- base64 is encoding, not encryption
- Secrets exposed to anyone with repo access
- Compliance violations
The Fix:
Option 1: External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: azure-keyvault
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: database-passwordOption 2: Sealed Secrets
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
spec:
encryptedData:
password: AgBy8hCi... # Actually encryptedOption 3: SOPS
# Encrypted with SOPS - safe to commit
password: ENC[AES256_GCM,data:xxx,iv:yyy,tag:zzz,type:str]---
Anti-Pattern 4: Hardcoded Environment Values
The Problem:
# DON'T DO THIS - Hardcoded for each environment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3 # What about dev? staging?
template:
spec:
containers:
- name: app
env:
- name: DATABASE_URL
value: "postgres://prod-db:5432/app" # Hardcoded!
resources:
limits:
memory: "2Gi" # Same for all envs?The Fix:
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 1 # Overridden per environment
template:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
# overlays/production/kustomization.yaml
replicas:
- name: app
count: 3
# overlays/production/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_URL: "postgres://prod-db:5432/app"---
Workflow Anti-Patterns
Anti-Pattern 5: Bypassing Git for "Quick Fixes"
The Problem:
# "It's just a quick fix, I'll update Git later"
kubectl apply -f hotfix.yaml
# ... 3 months later, no Git update, forgottenWhy It's Bad:
- "Later" never comes
- Creates undocumented drift
- Next sync overwrites the fix
- Knowledge lost
The Fix:
1. Disable direct kubectl access to production 2. Enable self-heal in ArgoCD:
syncPolicy:
automated:
selfHeal: true3. Fast-track PR process for hotfixes 4. Emergency runbook that includes Git steps
---
Anti-Pattern 6: No Sync Windows
The Problem:
# Automated sync with no restrictions
syncPolicy:
automated:
prune: true
selfHeal: true
# Deploys at 3 AM on Friday before a holiday...Why It's Bad:
- Deployments during low-staffing periods
- No change control
- Compliance issues
- Incidents during off-hours
The Fix:
# ArgoCD Project with sync windows
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
spec:
syncWindows:
# Allow syncs Monday-Thursday, 9 AM - 5 PM
- kind: allow
schedule: "0 9 * * 1-4"
duration: 8h
applications: ["*"]
# Deny all syncs on weekends
- kind: deny
schedule: "0 0 * * 0,6"
duration: 24h
applications: ["*"]---
Anti-Pattern 7: Monolithic Applications
The Problem:
# Single Application for entire platform
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: everything
spec:
source:
path: manifests/ # 500+ resources!Why It's Bad:
- Single failure affects everything
- Long sync times
- Difficult to track changes
- No granular rollback
- Complex RBAC
The Fix:
# App of Apps pattern
# Root application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: platform
spec:
source:
path: apps/
---
# Individual applications
# apps/frontend.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
spec:
source:
path: manifests/frontend/
---
# apps/backend.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: backend
spec:
source:
path: manifests/backend/---
Anti-Pattern 8: Ignoring Drift Detection
The Problem:
# "OutOfSync is normal, we ignore it"
syncPolicy:
automated: null # No auto-sync
# No alerts configured
# No regular reconciliationWhy It's Bad:
- Security vulnerabilities unpatched
- Configuration creep
- Disaster recovery compromised
- Git becomes stale
The Fix:
1. Configure alerts for OutOfSync status 2. Schedule regular syncs even if manual 3. Use diff commands in CI:
argocd app diff myapp --exit-code
if [ $? -ne 0 ]; then
echo "Drift detected!"
# Send alert
fi4. Review OutOfSync apps weekly
---
Architecture Anti-Patterns
Anti-Pattern 9: Single Repository for All Environments
The Problem:
monorepo/
├── prod-secrets.yaml # Production secrets
├── dev-secrets.yaml # Dev secrets
├── manifests/ # Same access for allWhy It's Bad:
- Everyone with repo access sees production secrets
- No separation of duties
- Compliance violations
- Accidental production changes
The Fix:
# Separate repositories with different access
infra-config/ # Platform team only
├── applications/
└── base-values/
prod-values/ # Production team + approvals
├── secrets/
└── values/
dev-values/ # Developers
├── secrets/
└── values/---
Anti-Pattern 10: No Health Checks
The Problem:
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
# Syncs and reports "Healthy" immediately
# Doesn't wait for pods to be readyWhy It's Bad:
- Deployment appears successful when it's not
- Rolling updates continue despite failures
- No automatic rollback trigger
The Fix:
# Proper health checks in Deployment
spec:
template:
spec:
containers:
- name: app
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20# ArgoCD sync with health check
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# ArgoCD will wait for resources to be healthy---
Anti-Pattern 11: No Rollback Strategy
The Problem:
# On deployment failure:
# "Let me just push another commit to fix it"
# ... 30 minutes of debugging while production is downWhy It's Bad:
- Extended downtime
- Panic-driven changes
- More errors from rushed fixes
The Fix:
Immediate rollback via Git:
# Option 1: Revert commit
git revert HEAD
git push
# Option 2: Reset to known good
git reset --hard v1.2.2
git push --force # If protected, use revert
# Option 3: ArgoCD CLI
argocd app rollback myapp 2 # Rollback to revision 2Automated rollback with Argo Rollouts:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: myapp
# Automatic rollback on analysis failure---
Anti-Pattern Checklist
Before deploying, verify:
- [ ] No
kubectl applyto production - [ ] No
:latestor mutable tags - [ ] No secrets in Git (plain or base64)
- [ ] Environment-specific values in overlays
- [ ] Sync windows configured for production
- [ ] Applications are granular (not monolithic)
- [ ] Drift alerts configured
- [ ] Health checks defined
- [ ] Rollback procedure documented
- [ ] Repository access properly scoped
Summary
| Anti-Pattern | Impact | Prevention |
|---|---|---|
| Imperative commands | Drift, no audit | Self-heal, block kubectl |
| Mutable tags | Unknown state | SemVer, digests |
| Secrets in Git | Security breach | External secrets |
| Hardcoded values | Inflexibility | Kustomize overlays |
| Bypassing Git | Lost changes | Self-heal, RBAC |
| No sync windows | Risky deployments | Project policies |
| Monolithic apps | Blast radius | App of Apps |
| Ignoring drift | Security risk | Alerts, audits |
| Single repo | Access issues | Multi-repo pattern |
| No health checks | False success | Probes, sync health |
| No rollback plan | Extended outages | Documented runbook |
Azure Arc GitOps Integration with ArgoCD
Comprehensive guide for deploying and managing ArgoCD via Azure Arc-enabled Kubernetes and Azure Kubernetes Service (AKS) using Azure's managed GitOps extension.
Overview
Azure provides a managed ArgoCD experience through the Microsoft.ArgoCD cluster extension. This enables GitOps workflows on:
- Azure Arc-enabled Kubernetes: On-premises, multi-cloud, or edge
Kubernetes clusters connected to Azure
- Azure Kubernetes Service (AKS): Azure's managed Kubernetes offering
Key Benefits
| Benefit | Description |
|---|---|
| Managed Installation | Azure handles ArgoCD deployment and upgrades |
| Workload Identity | Native Azure AD integration without managing secrets |
| Multi-Cluster | Consistent GitOps across hybrid environments |
| Azure Integration | Works with Azure Key Vault, ACR, and Azure AD |
| High Availability | Built-in HA mode with 3-node support |
---
Prerequisites
Azure Arc-enabled Kubernetes
1. Kubernetes cluster connected to Azure Arc:
# Connect cluster to Azure Arc
az connectedk8s connect --name <cluster-name> \
--resource-group <resource-group>2. Required permissions:
Microsoft.Kubernetes/connectedClusters(read/write)Microsoft.KubernetesConfiguration/extensions(read/write)
Azure Kubernetes Service (AKS)
1. MSI-based AKS cluster (not SPN):
# Create MSI-based AKS cluster
az aks create --resource-group <rg> --name <cluster> \
--enable-managed-identity
# Convert existing SPN cluster to MSI
az aks update -g <rg> -n <cluster> --enable-managed-identity2. Required permissions:
Microsoft.ContainerService/managedClusters(read/write)Microsoft.KubernetesConfiguration/extensions(read/write)
Common Requirements
# Register Azure providers
az provider register --namespace Microsoft.Kubernetes
az provider register --namespace Microsoft.ContainerService
az provider register --namespace Microsoft.KubernetesConfiguration
# Install CLI extensions
az extension add -n k8s-configuration
az extension add -n k8s-extension
# Verify registration (wait for 'Registered' state)
az provider show -n Microsoft.KubernetesConfiguration -o table---
Network Requirements
The GitOps agents require outbound access to:
| Endpoint | Purpose |
|---|---|
management.azure.com | Azure Resource Manager communication |
<region>.dp.kubernetesconfiguration.azure.com | Configuration data plane |
login.microsoftonline.com | Azure AD token refresh |
mcr.microsoft.com | Container image pulls |
| Git repository (port 22 or 443) | Source code sync |
---
Installation Methods
Method 1: Simple Installation (Single Node)
For development or single-node clusters:
az k8s-extension create \
--resource-group <resource-group> \
--cluster-name <cluster-name> \
--cluster-type managedClusters \
--name argocd \
--extension-type Microsoft.ArgoCD \
--release-train preview \
--config deployWithHighAvailability=false \
--config namespaceInstall=false \
--config "config-maps.argocd-cmd-params-cm.data.application\.namespaces=namespace1,namespace2"Parameters:
| Parameter | Description |
|---|---|
deployWithHighAvailability=false | Single-node deployment |
namespaceInstall=false | Cluster-wide ArgoCD access |
application.namespaces | Namespaces where ArgoCD can detect Applications |
Method 2: High Availability Installation (Production)
For production with 3+ nodes:
az k8s-extension create \
--resource-group <resource-group> \
--cluster-name <cluster-name> \
--cluster-type managedClusters \
--name argocd \
--extension-type Microsoft.ArgoCD \
--release-train preview \
--config namespaceInstall=false \
--config "config-maps.argocd-cmd-params-cm.data.application\.namespaces=default,argocd"Method 3: Namespace-Scoped Installation
For multi-tenant clusters with isolated ArgoCD instances:
az k8s-extension create \
--resource-group <resource-group> \
--cluster-name <cluster-name> \
--cluster-type managedClusters \
--name argocd-team-a \
--extension-type Microsoft.ArgoCD \
--release-train preview \
--config namespaceInstall=true \
--target-namespace team-a-argocd---
Workload Identity Integration (Recommended for Production)
Workload identity enables Azure AD authentication without managing secrets.
Bicep Template
var clusterName = '<aks-or-arc-cluster-name>'
var workloadIdentityClientId = '<managed-identity-client-id>'
var ssoWorkloadIdentityClientId = '<sso-managed-identity-client-id>'
var url = 'https://<public-ip-for-argocd-ui>/'
var oidcConfig = '''
name: Azure
issuer: https://login.microsoftonline.com/<your-tenant-id>/v2.0
clientID: <sso-client-id>
azure:
useWorkloadIdentity: true
requestedIDTokenClaims:
groups:
essential: true
requestedScopes:
- openid
- profile
- email
'''
var defaultPolicy = 'role:readonly'
var policy = '''
p, role:org-admin, applications, *, */*, allow
p, role:org-admin, clusters, get, *, allow
p, role:org-admin, repositories, get, *, allow
p, role:org-admin, repositories, create, *, allow
p, role:org-admin, repositories, update, *, allow
p, role:org-admin, repositories, delete, *, allow
g, <entra-group-id>, role:org-admin
'''
resource cluster 'Microsoft.ContainerService/managedClusters@2024-10-01' existing = {
name: clusterName
}
resource extension 'Microsoft.KubernetesConfiguration/extensions@2023-05-01' = {
name: 'argocd'
scope: cluster
properties: {
extensionType: 'Microsoft.ArgoCD'
releaseTrain: 'preview'
configurationSettings: {
'workloadIdentity.enable': 'true'
'workloadIdentity.clientId': workloadIdentityClientId
'workloadIdentity.entraSSOClientId': ssoWorkloadIdentityClientId
'config-maps.argocd-cm.data.oidc\\.config': oidcConfig
'config-maps.argocd-cm.data.url': url
'config-maps.argocd-rbac-cm.data.policy\\.default': defaultPolicy
'config-maps.argocd-rbac-cm.data.policy\\.csv': policy
'config-maps.argocd-cmd-params-cm.data.application\\.namespaces': 'default, argocd'
}
}
}Deploy with Bicep
az deployment group create \
--resource-group <resource-group> \
--template-file argocd-extension.bicepSetup Workload Identity Credentials
1. Retrieve OIDC issuer URL:
# For AKS
az aks show -n <cluster> -g <rg> --query "oidcIssuerProfile.issuerUrl" -o tsv
# For Arc-enabled Kubernetes
az connectedk8s show -n <cluster> -g <rg> --query "oidcIssuerProfile.issuerUrl" -o tsv2. Create managed identity:
az identity create --name argocd-identity --resource-group <rg>3. Create federated credential:
az identity federated-credential create \
--name argocd-federated \
--identity-name argocd-identity \
--resource-group <rg> \
--issuer <oidc-issuer-url> \
--subject system:serviceaccount:argocd:source-controller \
--audience api://AzureADTokenExchange4. Grant ACR permissions (if using Azure Container Registry):
# For ABAC-enabled registries
az role assignment create \
--role "Container Registry Repository Reader" \
--assignee <identity-client-id> \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/<acr>
# For non-ABAC registries
az role assignment create \
--role "AcrPull" \
--assignee <identity-client-id> \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/<acr>---
Accessing ArgoCD UI
Option 1: LoadBalancer Service
kubectl -n argocd expose service argocd-server \
--type LoadBalancer \
--name argocd-server-lb \
--port 80 \
--target-port 8080Option 2: Ingress Controller
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server
namespace: argocd
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
rules:
- host: argocd.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443Option 3: Port Forward (Development)
kubectl port-forward svc/argocd-server -n argocd 8080:443---
Deploying Applications
Example: AKS Store Demo
kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: aks-store-demo
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Azure-Samples/aks-store-demo.git
targetRevision: HEAD
path: kustomize/overlays/dev
syncPolicy:
automated: {}
destination:
namespace: pets
server: https://kubernetes.default.svc
EOFMulti-Source with Azure Container Registry
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
sources:
# Helm chart from ACR
- repoURL: <acr-name>.azurecr.io/helm
chart: myapp
targetRevision: 1.0.0
helm:
valueFiles:
- $values/overlays/prod/values.yaml
# Values from Git
- repoURL: https://github.com/org/config.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: myapp---
Updating Configuration
Update ArgoCD configmaps through the extension (not directly via kubectl):
az k8s-extension update \
--resource-group <resource-group> \
--cluster-name <cluster-name> \
--cluster-type managedClusters \
--name argocd \
--config "config-maps.argocd-cm.data.url=https://<new-public-ip>/auth/callback"---
Connecting to Private ACR
For private Azure Container Registry access with workload identity:
1. Use workload identity (configured above) 2. Add repository in ArgoCD:
argocd repo add <acr-name>.azurecr.io \
--type helm \
--name azure-acr \
--enable-oci---
Deleting the Extension
az k8s-extension delete \
-g <resource-group> \
-c <cluster-name> \
-n argocd \
-t managedClusters \
--yes---
Comparison: Azure Extension vs Manual Installation
| Aspect | Azure Extension | Manual Installation |
|---|---|---|
| Installation | az k8s-extension create | kubectl apply or Helm |
| Upgrades | Managed by Azure | Manual |
| Workload Identity | Built-in support | Manual configuration |
| Azure AD SSO | Simplified setup | Complex OIDC config |
| Support | Azure support included | Community support |
| Customization | Limited to extension params | Full control |
| Multi-cluster | Centralized Azure management | Per-cluster management |
---
Troubleshooting
Extension Installation Failed
# Check extension status
az k8s-extension show \
-g <rg> -c <cluster> -t managedClusters \
-n argocd
# Check ArgoCD pods
kubectl get pods -n argocd
# Check extension operator logs
kubectl logs -n azure-arc -l app.kubernetes.io/component=extension-managerWorkload Identity Issues
# Verify federated credential
az identity federated-credential list \
--identity-name argocd-identity \
--resource-group <rg>
# Check service account annotation
kubectl get sa -n argocd source-controller -o yamlSync Failures
# Check ArgoCD application status
argocd app get <app-name>
# Check repo server logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-repo-server---
Best Practices for Azure GitOps
1. Use Workload Identity: Avoid storing secrets; use Azure AD authentication 2. Private Endpoints: Use Azure Private Link for ACR and Key Vault 3. Azure Policy: Enforce GitOps compliance with Azure Policy 4. Azure Monitor: Integrate ArgoCD metrics with Azure Monitor 5. Separate Environments: Use different resource groups for dev/staging/prod 6. RBAC: Map Azure AD groups to ArgoCD roles
---
References
GitOps Core Principles
Deep dive into the four foundational principles of GitOps as defined by the OpenGitOps project (CNCF).
The Four Pillars of GitOps
Principle 1: Declarative
"A system managed by GitOps must have its desired state expressed declaratively."
What This Means
- Declarative: Describe WHAT you want, not HOW to achieve it
- Imperative (opposite): Step-by-step instructions to reach a state
Examples
Declarative (GitOps):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
spec:
containers:
- name: nginx
image: nginx:1.21
resources:
limits:
memory: "128Mi"
cpu: "500m"Imperative (NOT GitOps):
kubectl run nginx --image=nginx:1.21
kubectl scale deployment nginx --replicas=3
kubectl set resources deployment nginx -c=nginx --limits=memory=128Mi,cpu=500mWhy Declarative Matters
| Benefit | Description |
|---|---|
| Reproducibility | Same manifest = same result |
| Auditability | Changes tracked in Git |
| Comparison | Easy diff between desired and actual |
| Recovery | Reapply manifest to restore state |
Declarative Tools
| Tool | Purpose |
|---|---|
| Kubernetes YAML | Native resource definitions |
| Kustomize | Overlay-based customization |
| Helm | Templated charts |
| Jsonnet | Data templating language |
| CUE | Configuration unification |
| Terraform | Infrastructure as Code |
---
Principle 2: Versioned and Immutable
"Desired state is stored in a way that enforces immutability, versioning, and retains a complete version history."
Git as the Version Control System
Git provides:
- Immutability: Commits are content-addressed (SHA)
- Versioning: Full history of changes
- Branching: Parallel development streams
- Audit trail: Who changed what, when, why
Version Control Best Practices
# Good commit message structure
git commit -m "feat(nginx): increase replicas to 3 for high availability
- Scaling nginx deployment from 1 to 3 replicas
- Adding pod anti-affinity for distribution
- Tested in staging environment
Relates to: TICKET-123"Immutability Patterns
Container Images:
# GOOD: Immutable tag
image: nginx:1.21.6
# BAD: Mutable tag
image: nginx:latestGit References:
# GOOD: Specific commit or tag
targetRevision: v1.2.3
targetRevision: abc123def
# RISKY: Branch (mutable)
targetRevision: mainVersion History Benefits
# View deployment history
git log --oneline manifests/
# Compare versions
git diff v1.0.0..v1.1.0 -- manifests/
# Find when change was introduced
git bisect start
git bisect bad HEAD
git bisect good v1.0.0---
Principle 3: Pulled Automatically
"Software agents automatically pull the desired state declarations from the source."
Pull vs Push Model
┌──────────────────────────────────────────────────────────────────┐
│ PUSH MODEL (Traditional) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ CI/CD Server │
│ │ │
│ │ kubectl apply │
│ │ (requires cluster credentials) │
│ ▼ │
│ Kubernetes Cluster │
│ │
│ Issues: │
│ - Credentials exposed in CI │
│ - No continuous reconciliation │
│ - Drift goes undetected │
│ │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ PULL MODEL (GitOps) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Git Repository ◄──────────────┐ │
│ │ │ │
│ │ (pull/watch) │ Developer pushes │
│ ▼ │ │
│ GitOps Controller ────────────┘ │
│ (inside cluster) │
│ │ │
│ │ kubectl apply │
│ │ (internal credentials) │
│ ▼ │
│ Kubernetes Cluster │
│ │
│ Benefits: │
│ - Credentials stay in cluster │
│ - Continuous reconciliation │
│ - Automatic drift detection │
│ │
└──────────────────────────────────────────────────────────────────┘Automatic Pull Mechanisms
Polling (Default):
# ArgoCD application controller settings
spec:
syncPolicy:
automated: {}
# Default poll interval: 3 minutesWebhook (Recommended for Production):
# Configure webhook for instant updates
apiVersion: v1
kind: Secret
metadata:
name: argocd-webhook
namespace: argocd
data:
github.secret: <base64-encoded-secret>Git Generators (ApplicationSets):
spec:
generators:
- git:
repoURL: https://github.com/org/repo.git
revision: HEAD
directories:
- path: apps/*Security Benefits of Pull
| Aspect | Push Model | Pull Model |
|---|---|---|
| Credential Location | CI server | Cluster only |
| Attack Surface | External access required | Internal only |
| Audit | CI logs | Git + controller logs |
| Blast Radius | CI compromise = cluster access | Limited to controller |
---
Principle 4: Continuously Reconciled
"Software agents continuously observe actual system state and attempt to apply the desired state."
The Reconciliation Loop
┌─────────────────────────────────────────────────────────────────┐
│ CONTINUOUS RECONCILIATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Git (Desired)│ │
│ │ State │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Compare │◄────│ Observe │ │
│ │ (Diff) │ │ Actual │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ Apply │───────────┘ │
│ │ Changes │ Repeat continuously │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Self-Healing Capabilities
spec:
syncPolicy:
automated:
selfHeal: true # Automatically revert manual changes
prune: true # Remove resources not in GitSelf-Heal Scenarios:
| Manual Change | GitOps Response |
|---|---|
kubectl scale deploy --replicas=1 | Restored to Git-defined replicas |
kubectl delete pod | Pod recreated (normal K8s) |
kubectl edit configmap | Reverted to Git version |
kubectl delete deployment | Deployment recreated |
Drift Detection
Types of Drift:
1. Configuration Drift: Resource spec differs from Git 2. State Drift: Resource status unhealthy 3. Missing Resources: Resources deleted manually 4. Extra Resources: Resources created outside Git
Detecting Drift:
# ArgoCD diff command
argocd app diff myapp
# Flux reconciliation
flux reconcile kustomization myapp --with-sourceReconciliation Intervals
| Tool | Default Interval | Configurable |
|---|---|---|
| ArgoCD | 3 minutes | Yes, via timeout.reconciliation |
| Flux | 10 minutes | Yes, per Kustomization/HelmRelease |
| Kargo | Event-driven | Webhook-based |
Example Configuration:
# ArgoCD ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
data:
timeout.reconciliation: 180s # 3 minutes# Flux Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
spec:
interval: 5m # Check every 5 minutes---
Putting It All Together
The Complete GitOps Workflow
1. Developer creates PR with DECLARATIVE changes
│
▼
2. Changes reviewed and VERSIONED in Git
│
▼
3. GitOps controller PULLS changes automatically
│
▼
4. Controller CONTINUOUSLY RECONCILES cluster state
│
▼
5. Drift detected? → Auto-heal OR alertCompliance with Principles Checklist
- [ ] All configurations are YAML/HCL (declarative)
- [ ] All changes go through Git PR (versioned)
- [ ] No
kubectl applyfrom laptops (pulled) - [ ] Self-heal enabled (continuously reconciled)
- [ ] Drift alerts configured (continuously reconciled)
References
GitOps Patterns and Practices
Comprehensive guide to repository structures, branching strategies, and deployment patterns for GitOps implementations.
Repository Structure Patterns
Pattern 1: Monorepo
All applications and environments in a single repository.
gitops-monorepo/
├── apps/
│ ├── frontend/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── dev/
│ │ │ ├── kustomization.yaml
│ │ │ └── replica-patch.yaml
│ │ ├── staging/
│ │ └── production/
│ ├── backend/
│ └── database/
├── infrastructure/
│ ├── cert-manager/
│ ├── ingress-nginx/
│ └── monitoring/
├── clusters/
│ ├── dev/
│ ├── staging/
│ └── production/
└── README.mdPros:
- Single source of truth
- Easy cross-application changes
- Atomic multi-app deployments
- Simplified tooling
Cons:
- Large repository over time
- Broad access permissions needed
- CI/CD triggers for all changes
- Potential merge conflicts
Best For: Small-medium teams, tightly coupled applications
---
Pattern 2: Polyrepo (Multi-Repository)
Separate repositories per application or concern.
# Application repositories
app-frontend/
├── src/
├── Dockerfile
└── k8s/
├── base/
└── overlays/
app-backend/
├── src/
├── Dockerfile
└── k8s/
# Infrastructure repository
platform-infrastructure/
├── cert-manager/
├── ingress/
└── monitoring/
# Cluster configuration
cluster-config/
├── dev/
├── staging/
└── production/Pros:
- Fine-grained access control
- Independent release cycles
- Smaller, focused repositories
- Team autonomy
Cons:
- Harder to coordinate changes
- More repositories to manage
- Complex dependency tracking
- Potential version drift
Best For: Large organizations, microservices, multiple teams
---
Pattern 3: App of Apps (Umbrella Pattern)
A parent Application manages child Applications.
# Root Application (apps/root-app.yaml)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/gitops-config.git
targetRevision: HEAD
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocdgitops-config/
├── apps/
│ ├── frontend.yaml # Application CR
│ ├── backend.yaml # Application CR
│ ├── monitoring.yaml # Application CR
│ └── kustomization.yaml
└── manifests/
├── frontend/
├── backend/
└── monitoring/Benefits:
- Hierarchical organization
- Single sync point
- Environment-specific app sets
- Easy to add/remove apps
---
Pattern 4: Multi-Repository with Values Separation
Separates infrastructure definitions from environment-specific values.
# Repository 1: Infrastructure (infra-team/)
infra-team/
├── applications/
│ ├── nginx/
│ │ ├── applicationset.yaml
│ │ └── base-values.yaml
│ └── prometheus/
└── applicationsets/
# Repository 2: Values (argo-cd-helm-values/)
argo-cd-helm-values/
├── dev/
│ ├── nginx/
│ │ └── values.yaml
│ └── prometheus/
├── staging/
└── production/ApplicationSet with Multi-Source:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: nginx
spec:
generators:
- list:
elements:
- cluster: dev
url: https://dev-cluster
- cluster: prod
url: https://prod-cluster
template:
spec:
sources:
- repoURL: https://charts.bitnami.com/bitnami
chart: nginx
targetRevision: 15.0.0
helm:
valueFiles:
- $values/{{cluster}}/nginx/values.yaml
- repoURL: https://github.com/org/argo-cd-helm-values.git
targetRevision: main
ref: valuesBenefits:
- Security boundary between repos
- Different RBAC per environment
- Separation of concerns
- Easier secret management
---
Branching Strategies
Strategy 1: Environment Branches
main ──────────────────────────────────────► Production
│
└── staging ─────────────────────────────► Staging
│
└── develop ───────────────────────► DevelopmentWorkflow:
1. Develop on develop branch 2. Merge to staging for testing 3. Merge to main for production
Pros: Clear environment mapping Cons: Merge conflicts, branch maintenance
---
Strategy 2: Trunk-Based with Directory Overlays
main (single branch)
├── base/ # Shared configuration
├── overlays/
│ ├── dev/ # Dev-specific patches
│ ├── staging/ # Staging-specific patches
│ └── production/ # Prod-specific patchesWorkflow:
1. All changes go to main 2. Kustomize overlays handle environment differences 3. GitOps controller watches specific paths
Pros: Simple, fewer branches, atomic changes Cons: Requires good overlay discipline
---
Strategy 3: Release Branches
main
│
├── release/v1.0.0 ──► Production (v1.0)
├── release/v1.1.0 ──► Production (v1.1)
└── release/v2.0.0 ──► Production (v2.0)Workflow:
1. Develop on main 2. Create release branch for deployment 3. Hotfixes on release branches 4. Cherry-pick to main
Pros: Clear versioning, rollback by switching branches Cons: Branch proliferation, merge complexity
---
Strategy 4: GitFlow for GitOps
main ────────────────────────────────────────► Production
▲
│
│ merge
│
develop ─────────────────────────────────────► Development
▲ ▲
│ │
feature/ release/
branches branchesBest For: Complex release processes, multiple parallel versions
---
Deployment Patterns
Progressive Delivery Patterns
Blue-Green Deployment
# Blue deployment (current production)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-blue
spec:
source:
path: manifests/blue
destination:
namespace: myapp-blue
---
# Green deployment (new version)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-green
spec:
source:
path: manifests/green
destination:
namespace: myapp-greenTraffic Switch:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
backend:
service:
name: myapp-green # Switch from blue to green
port:
number: 80Canary Deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to canary
- pause: {duration: 5m}
- setWeight: 30
- pause: {duration: 10m}
- setWeight: 50
- pause: {duration: 10m}
trafficRouting:
nginx:
stableIngress: myapp-stableWave-Based Deployment
# Wave 1: Infrastructure
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1"
# Wave 2: Database migrations
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
# Wave 3: Application
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"
# Wave 4: Post-deployment jobs
metadata:
annotations:
argocd.argoproj.io/sync-wave: "2"---
Multi-Cluster Patterns
Hub and Spoke
┌─────────────────┐
│ Hub Cluster │
│ (ArgoCD) │
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Spoke 1 │ │ Spoke 2 │ │ Spoke 3 │
│ (Dev) │ │ (Staging)│ │ (Prod) │
└───────────┘ └───────────┘ └───────────┘apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
environment: production
template:
spec:
destination:
server: '{{server}}'
namespace: myappPull-Based Multi-Cluster
Each cluster has its own GitOps controller:
Git Repository
│
├──────────────────┬──────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Cluster 1 │ │ Cluster 2 │ │ Cluster 3 │
│ ArgoCD │ │ Flux │ │ ArgoCD │
└───────────┘ └───────────┘ └───────────┘---
Environment Promotion Pattern
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Dev │────►│ Staging │────►│ Production │
│ │ │ │ │ │
│ Auto-deploy │ │ Auto-deploy │ │ Manual gate │
│ from main │ │ after dev │ │ approval │
└──────────────┘ └──────────────┘ └──────────────┘Kargo Implementation:
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: production
spec:
requestedFreight:
- origin:
kind: Warehouse
name: main-warehouse
sources:
stages:
- staging
requiredSoakTime: 24h # Must be stable in staging for 24h---
Best Practices Summary
Repository Best Practices
| Practice | Description |
|---|---|
| README in every directory | Document purpose and ownership |
| CODEOWNERS file | Define approval requirements |
| Branch protection | Require PR reviews |
| Semantic versioning | For releases and tags |
| Gitignore | Exclude generated files |
Branching Best Practices
| Practice | Description |
|---|---|
| Keep main deployable | Always production-ready |
| Short-lived branches | Reduce merge conflicts |
| Descriptive branch names | feature/add-redis, fix/memory-leak |
| Squash on merge | Clean history |
Deployment Best Practices
| Practice | Description |
|---|---|
| Progressive rollouts | Never deploy 100% immediately |
| Automated rollback | On health check failure |
| Sync windows | Control when deployments happen |
| Resource quotas | Prevent runaway deployments |
Quick Reference
Choose Your Pattern
| Scenario | Recommended Pattern |
|---|---|
| Small team, few apps | Monorepo + trunk-based |
| Large org, many teams | Polyrepo + App of Apps |
| Strict compliance | Multi-repo with values separation |
| Rapid iteration | Trunk-based + overlays |
| Complex releases | GitFlow or release branches |
GitOps Tooling Ecosystem
Comprehensive comparison of GitOps tools, their architectures, and use cases.
Tool Comparison Overview
| Feature | ArgoCD | Flux | Kargo |
|---|---|---|---|
| Primary Focus | Application deployment | Full GitOps toolkit | Progressive delivery |
| UI | Built-in web UI | Third-party (Weave GitOps) | Built-in web UI |
| Multi-tenancy | Projects, RBAC | Namespaced controllers | Projects, RBAC |
| Helm Support | Native | Native | Via ArgoCD integration |
| Kustomize Support | Native | Native | Via ArgoCD integration |
| Multi-Cluster | Centralized hub | Agent per cluster | Centralized |
| GitOps Model | Pull | Pull | Pull + Promotion |
| CNCF Status | Graduated | Graduated | Incubating |
| Best For | Visibility, multi-cluster | Lightweight, automation | Environment promotion |
---
ArgoCD
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ ArgoCD Components │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ API Server │◄──►│ Web UI │ │
│ │ (gRPC/REST) │ │ │ │
│ └────────┬─────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Repo Server │ │ Redis │ │
│ │ (Git operations) │ │ (Caching) │ │
│ └────────┬─────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Application │ │ Dex │ │
│ │ Controller │ │ (SSO/OIDC) │ │
│ │ (Reconciliation) │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Key Features
Application CRD:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/repo.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: trueApplicationSet (Multi-App Generation):
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-apps
spec:
generators:
- clusters: {} # All registered clusters
template:
spec:
destination:
server: '{{server}}'Strengths
- Rich web UI with visualization
- Multi-cluster management from single control plane
- SSO integration (OIDC, SAML, LDAP)
- ApplicationSets for templated deployments
- Extensive sync options and hooks
- Large community and ecosystem
Considerations
- Resource-intensive for large deployments
- Single point of failure (hub cluster)
- Requires dedicated namespace
Installation
Standard Installation:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yamlAzure Arc / AKS Managed Extension:
# Register providers
az provider register --namespace Microsoft.KubernetesConfiguration
az extension add -n k8s-extension
# Install ArgoCD as managed extension
az k8s-extension create \
--resource-group <rg> --cluster-name <cluster> \
--cluster-type managedClusters \
--name argocd \
--extension-type Microsoft.ArgoCD \
--release-train preview \
--config deployWithHighAvailability=falseBenefits of Azure managed extension:
- Managed upgrades and maintenance
- Native Azure AD workload identity integration
- Consistent multi-cluster management via Azure Arc
- See
azure-arc-integration.mdfor complete guide
---
Flux
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Flux Components │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Source Controller│ │ Kustomize │ │
│ │ (Git, Helm, OCI) │ │ Controller │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Helm Controller │ │ Notification │ │
│ │ │ │ Controller │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Image Automation │ │ Image Reflector │ │
│ │ Controller │ │ Controller │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Key Features
GitRepository Source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: myapp
namespace: flux-system
spec:
interval: 1m
url: https://github.com/org/repo.git
ref:
branch: main
secretRef:
name: git-credentialsKustomization (Flux CRD, not Kustomize):
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: myapp
path: ./manifests
prune: true
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: myapp
namespace: defaultHelmRelease:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
interval: 5m
chart:
spec:
chart: nginx
version: '15.x'
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
values:
replicaCount: 2Strengths
- Lightweight, modular architecture
- No single point of failure
- Native image automation (update manifests on new image)
- OCI registry support for storing configs
- Multi-tenancy via namespaces
- Terraform Controller integration
Considerations
- No built-in UI (requires Weave GitOps or similar)
- Steeper learning curve for CRD relationships
- Each cluster needs its own Flux instance
Installation
flux bootstrap github \
--owner=my-org \
--repository=fleet-infra \
--branch=main \
--path=clusters/my-cluster \
--personal---
Kargo
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Kargo Components │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Warehouse │ │ Stages │ │
│ │ (Artifact │ │ (Promotion │ │
│ │ Discovery) │ │ Targets) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Freight │ │
│ │ (Versioned artifact collection) │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Promotions │ │
│ │ (Move Freight through Stages) │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Key Features
Warehouse (Artifact Discovery):
apiVersion: kargo.akuity.io/v1alpha1
kind: Warehouse
metadata:
name: main-warehouse
namespace: myproject
spec:
subscriptions:
- image:
repoURL: ghcr.io/org/myapp
imageSelectionStrategy: SemVer
- git:
repoURL: https://github.com/org/config.git
branch: mainStage (Promotion Target):
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: staging
namespace: myproject
spec:
requestedFreight:
- origin:
kind: Warehouse
name: main-warehouse
sources:
direct: true
promotionTemplate:
spec:
steps:
- uses: git-clone
- uses: kustomize-set-image
- uses: git-commit
- uses: git-push
- uses: argocd-updateStrengths
- Purpose-built for environment promotion
- Coordinates image + config updates
- Verification (testing) between stages
- Soak time requirements
- Works alongside ArgoCD
- Progressive delivery native
Considerations
- Newer project (less mature)
- Requires ArgoCD or Flux for actual deployment
- Additional complexity layer
Installation
helm install kargo \
oci://ghcr.io/akuity/kargo-charts/kargo \
--namespace kargo \
--create-namespace---
Tool Selection Guide
Decision Matrix
| Requirement | Best Tool |
|---|---|
| Need rich UI for developers | ArgoCD |
| Lightweight, minimal footprint | Flux |
| Multi-cluster from single pane | ArgoCD |
| Image automation (auto-update on new image) | Flux |
| Environment promotion workflows | Kargo |
| SSO/OIDC integration | ArgoCD |
| GitOps for Terraform | Flux (TF Controller) |
| Large enterprise with compliance | ArgoCD + Kargo |
Architecture Recommendations
Small Team (< 5 developers):
Single cluster
│
└── Flux or ArgoCD (standalone)Medium Team (5-20 developers):
ArgoCD (hub cluster)
│
├── Dev cluster
├── Staging cluster
└── Prod clusterLarge Organization (20+ developers):
Kargo + ArgoCD
│
├── ArgoCD manages deployments
└── Kargo manages promotions
│
├── Dev stages
├── Staging stages (with verification)
└── Prod stages (with approval gates)---
Complementary Tools
Secrets Management
| Tool | Integration |
|---|---|
| External Secrets Operator | ArgoCD, Flux |
| Sealed Secrets | ArgoCD, Flux |
| SOPS | Flux native, ArgoCD plugin |
| HashiCorp Vault | Both via CSI or injector |
Progressive Delivery
| Tool | Use Case |
|---|---|
| Argo Rollouts | Canary, Blue-Green |
| Flagger | Works with Flux |
| Kargo | Multi-environment promotion |
Policy Enforcement
| Tool | Purpose |
|---|---|
| Kyverno | Kubernetes-native policies |
| OPA Gatekeeper | Rego-based policies |
| Datree | Pre-commit validation |
Observability
| Tool | Purpose |
|---|---|
| Prometheus | Metrics |
| Grafana | Dashboards |
| ArgoCD Notifications | Alerts |
| Flux Notification Controller | Alerts |
---
Migration Paths
From Helm/kubectl to ArgoCD
1. Export existing Helm releases as values files 2. Create ArgoCD Applications pointing to charts 3. Disable Helm Tiller/manual deployments 4. Enable automated sync
From ArgoCD to Flux
1. Export Applications as Flux Kustomizations 2. Deploy Flux controllers 3. Migrate repo credentials 4. Decommission ArgoCD
Adding Kargo to Existing ArgoCD
1. Install Kargo alongside ArgoCD 2. Create Warehouses for artifact sources 3. Create Stages matching your environments 4. Add kargo.akuity.io/authorized-stage annotations to ArgoCD Applications 5. Define promotion templates
---
Quick CLI Reference
ArgoCD
argocd app list
argocd app sync myapp
argocd app get myapp
argocd app diff myapp
argocd app history myapp
argocd app rollback myapp 2Flux
flux get kustomizations
flux reconcile kustomization myapp
flux get sources git
flux logs --kind=Kustomization --name=myapp
flux suspend kustomization myapp
flux resume kustomization myappKargo
kargo get stages --project myproject
kargo get freight --project myproject
kargo promote --project myproject --freight <id> --stage prod
kargo approve --project myproject --freight <id> --stage prodGitOps Troubleshooting Guide
Comprehensive debugging guide for common GitOps issues with ArgoCD, Flux, and Kubernetes.
Quick Diagnostic Commands
ArgoCD Quick Checks
# Application status overview
argocd app list
# Detailed app info
argocd app get myapp
# Show diff between Git and cluster
argocd app diff myapp
# Force refresh from Git
argocd app get myapp --refresh
# View sync history
argocd app history myapp
# Check ArgoCD components health
kubectl get pods -n argocdFlux Quick Checks
# Overall Flux status
flux check
# Kustomization status
flux get kustomizations -A
# Source status
flux get sources git -A
# Reconcile immediately
flux reconcile kustomization myapp --with-source
# View logs
flux logs --kind=Kustomization --name=myappKubernetes Quick Checks
# Pod status
kubectl get pods -n myapp
# Recent events
kubectl get events -n myapp --sort-by='.lastTimestamp'
# Describe problematic resource
kubectl describe deployment myapp -n myapp
# Pod logs
kubectl logs -l app=myapp -n myapp --tail=100---
Common Issues and Solutions
Issue 1: Application Stuck in "OutOfSync"
Symptoms:
- Application shows
OutOfSyncstatus - Sync button doesn't resolve the issue
- Diff shows unexpected differences
Diagnostic Steps:
# Step 1: View the diff
argocd app diff myapp
# Step 2: Check for ignored differences
argocd app get myapp -o yaml | grep -A 20 ignoreDifferences
# Step 3: Force a hard refresh
argocd app get myapp --hard-refreshCommon Causes and Fixes:
Cause A: Server-side modifications (mutating webhooks, controllers)
# Fix: Add ignoreDifferences to Application
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Ignored by HPA
- group: ""
kind: Service
jsonPointers:
- /spec/clusterIP # Auto-assignedCause B: Defaulting by Kubernetes API
# Fix: Use Server-Side Apply
spec:
syncPolicy:
syncOptions:
- ServerSideApply=trueCause C: Resource created outside Git
# Identify extra resources
argocd app resources myapp
# Either:
# 1. Add to Git
# 2. Enable pruning
# 3. Add to exclude patterns---
Issue 2: Sync Failed
Symptoms:
- Application shows
Sync Failed - Error message in sync operation
Diagnostic Steps:
# Step 1: Get sync status details
argocd app get myapp
# Step 2: View sync operation result
argocd app sync myapp --dry-run
# Step 3: Check ArgoCD controller logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controllerCommon Causes and Fixes:
Cause A: Invalid YAML/Manifest
# Validate manifests locally
kustomize build ./overlays/prod | kubectl apply --dry-run=client -f -
# Or for Helm
helm template myrelease ./chart --validateCause B: RBAC Permissions
# Check ArgoCD service account permissions
kubectl auth can-i create deployments --as=system:serviceaccount:argocd:argocd-application-controller -n myapp# Fix: Add namespace to AppProject destinations
spec:
destinations:
- namespace: myapp
server: https://kubernetes.default.svcCause C: Resource Conflict
# Check if resource exists with different manager
kubectl get deployment myapp -n myapp -o yaml | grep -A 5 managedFields# Fix: Force replace
spec:
syncPolicy:
syncOptions:
- Replace=true---
Issue 3: Application Degraded
Symptoms:
- Application shows
Degradedhealth status - Pods not running correctly
Diagnostic Steps:
# Step 1: Get health details
argocd app get myapp
# Step 2: Check pod status
kubectl get pods -n myapp
kubectl describe pod <pod-name> -n myapp
# Step 3: Check pod logs
kubectl logs <pod-name> -n myapp --previous # For crashed containersCommon Causes and Fixes:
Cause A: Image Pull Failure
# Check events
kubectl get events -n myapp | grep -i pull
# Verify image exists
docker pull myregistry/myapp:v1.0.0
# Check imagePullSecrets
kubectl get deployment myapp -n myapp -o yaml | grep -A 5 imagePullSecretsCause B: Resource Limits
# Check for OOMKilled
kubectl get pods -n myapp -o jsonpath='{.items[*].status.containerStatuses[*].lastState.terminated.reason}'
# Check resource usage
kubectl top pods -n myappCause C: Readiness Probe Failure
# Check probe configuration
kubectl get deployment myapp -n myapp -o yaml | grep -A 10 readinessProbe
# Test endpoint manually
kubectl exec -it <pod-name> -n myapp -- curl localhost:8080/health---
Issue 4: Repository Connection Failed
Symptoms:
- ArgoCD can't connect to Git repository
ComparisonErrororUnable to fetch repository
Diagnostic Steps:
# Step 1: Check repository status
argocd repo list
# Step 2: Test connection
argocd repo get https://github.com/org/repo.git
# Step 3: Check repo-server logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-repo-serverCommon Causes and Fixes:
Cause A: Authentication Failure
# Update credentials
argocd repo add https://github.com/org/repo.git \
--username git \
--password $GITHUB_TOKEN \
--upsertCause B: SSH Key Issues
# Check known hosts
argocd cert list
# Add SSH key
argocd repo add git@github.com:org/repo.git \
--ssh-private-key-path ~/.ssh/id_rsaCause C: Network/Firewall
# Test from repo-server pod
kubectl exec -it -n argocd <repo-server-pod> -- \
git ls-remote https://github.com/org/repo.git---
Issue 5: Webhook Not Triggering
Symptoms:
- Changes pushed to Git but no sync
- Waiting for poll interval
Diagnostic Steps:
# Step 1: Check webhook configuration in Git provider
# Step 2: Verify ArgoCD webhook endpoint
curl -X POST https://argocd.example.com/api/webhook
# Step 3: Check API server logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server | grep webhookCommon Causes and Fixes:
Cause A: Wrong Webhook URL
# Correct URL format
https://argocd.example.com/api/webhook
# NOT
https://argocd.example.com/webhook
https://argocd.example.com/api/v1/webhookCause B: Secret Mismatch
# Verify webhook secret in argocd-secret
kubectl get secret argocd-secret -n argocd -o yaml | grep webhookCause C: Ingress/Load Balancer Issue
# Check if webhook endpoint is reachable
curl -v https://argocd.example.com/api/webhook---
Issue 6: Slow Sync Performance
Symptoms:
- Syncs take a long time
- Timeouts during sync
- High resource usage on ArgoCD
Diagnostic Steps:
# Step 1: Check resource usage
kubectl top pods -n argocd
# Step 2: Check number of resources
argocd app resources myapp | wc -l
# Step 3: Check controller metrics
kubectl port-forward -n argocd svc/argocd-metrics 8082:8082
curl localhost:8082/metrics | grep argocd_appOptimization Steps:
1. Increase Controller Resources:
# argocd-application-controller deployment
resources:
limits:
cpu: "2"
memory: "2Gi"
requests:
cpu: "500m"
memory: "512Mi"2. Split Large Applications:
# Instead of one app with 500 resources
# Create multiple smaller apps3. Optimize Sync Options:
spec:
syncPolicy:
syncOptions:
- ApplyOutOfSyncOnly=true # Only sync changed resources4. Adjust Reconciliation Timeout:
# In argocd-cm ConfigMap
data:
timeout.reconciliation: 300s---
Issue 7: Multi-Cluster Connection Issues
Symptoms:
- External cluster shows as disconnected
- Applications targeting external cluster fail
Diagnostic Steps:
# Step 1: List clusters
argocd cluster list
# Step 2: Check cluster status
argocd cluster get https://external-cluster:6443
# Step 3: Verify cluster secret
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=clusterCommon Causes and Fixes:
Cause A: Expired Credentials
# Rotate cluster credentials
argocd cluster rotate-auth https://external-cluster:6443Cause B: Network Connectivity
# Test from ArgoCD pod
kubectl exec -it -n argocd <application-controller-pod> -- \
curl -k https://external-cluster:6443/healthzCause C: Certificate Issues
# Re-add cluster with updated certs
argocd cluster add external-context --name external-cluster---
Debugging Checklist
Pre-Sync Checklist
- [ ] Manifests are valid YAML
- [ ] Image tags exist and are pullable
- [ ] Secrets/ConfigMaps referenced exist
- [ ] Namespace exists or
CreateNamespace=true - [ ] RBAC allows ArgoCD to create resources
- [ ] Resource quotas won't block creation
Post-Failure Checklist
- [ ] Check ArgoCD UI for error messages
- [ ] Review
argocd app diffoutput - [ ] Check Kubernetes events in target namespace
- [ ] Review ArgoCD controller logs
- [ ] Verify Git repository is accessible
- [ ] Check for webhook delivery failures
Performance Checklist
- [ ] Applications are appropriately sized
- [ ]
ApplyOutOfSyncOnlyenabled where appropriate - [ ] Controller resources adequate
- [ ] Redis cache functioning
- [ ] Repository server not overloaded
---
Log Locations
| Component | How to Access |
|---|---|
| ArgoCD API Server | kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server |
| ArgoCD Controller | kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller |
| ArgoCD Repo Server | kubectl logs -n argocd -l app.kubernetes.io/name=argocd-repo-server |
| Flux Source Controller | kubectl logs -n flux-system -l app=source-controller |
| Flux Kustomize Controller | kubectl logs -n flux-system -l app=kustomize-controller |
---
Emergency Procedures
Force Sync (Override Errors)
# ArgoCD
argocd app sync myapp --force --prune
# Flux
flux reconcile kustomization myapp --forceDisable Auto-Sync (Stop Reconciliation)
# ArgoCD - patch application
argocd app set myapp --sync-policy none
# Flux - suspend kustomization
flux suspend kustomization myappEmergency Rollback
# ArgoCD
argocd app rollback myapp <revision>
# Git-based (works for any tool)
git revert HEAD
git pushNuclear Option (Delete and Recreate)
# WARNING: Causes downtime
argocd app delete myapp --cascade=false # Keep resources
# Fix configuration
argocd app create myapp ... # Recreate application#!/usr/bin/env bash
#
# GitOps Health Check Script
# Validates GitOps setup and checks for common issues
#
# Usage:
# ./gitops-health-check.sh # Full check
# ./gitops-health-check.sh --argocd # ArgoCD only
# ./gitops-health-check.sh --flux # Flux only
# ./gitops-health-check.sh --manifests ./path # Validate manifests
#
# Requirements:
# - kubectl configured with cluster access
# - argocd CLI (for ArgoCD checks)
# - flux CLI (for Flux checks)
# - kustomize (for manifest validation)
# - helm (for chart validation)
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Counters
PASSED=0
FAILED=0
WARNINGS=0
# Functions
print_header() {
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n"
}
print_check() {
echo -ne " Checking: $1... "
}
pass() {
echo -e "${GREEN}✓ PASS${NC}"
((PASSED++))
}
fail() {
echo -e "${RED}✗ FAIL${NC}"
echo -e " ${RED}→ $1${NC}"
((FAILED++))
}
warn() {
echo -e "${YELLOW}⚠ WARN${NC}"
echo -e " ${YELLOW}→ $1${NC}"
((WARNINGS++))
}
skip() {
echo -e "${YELLOW}○ SKIP${NC}"
echo -e " ${YELLOW}→ $1${NC}"
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# ============================================
# KUBECTL / CLUSTER CHECKS
# ============================================
check_cluster_connectivity() {
print_header "CLUSTER CONNECTIVITY"
print_check "kubectl available"
if command_exists kubectl; then
pass
else
fail "kubectl not found in PATH"
return 1
fi
print_check "Cluster connection"
if kubectl cluster-info >/dev/null 2>&1; then
pass
CLUSTER_CONTEXT=$(kubectl config current-context)
echo -e " ${BLUE}→ Context: ${CLUSTER_CONTEXT}${NC}"
else
fail "Cannot connect to cluster"
return 1
fi
print_check "Cluster version"
if VERSION=$(kubectl version --short 2>/dev/null | grep "Server" | awk '{print $3}'); then
pass
echo -e " ${BLUE}→ Server: ${VERSION}${NC}"
else
warn "Could not determine cluster version"
fi
}
# ============================================
# ARGOCD CHECKS
# ============================================
check_argocd() {
print_header "ARGOCD HEALTH"
# Check if ArgoCD is installed
print_check "ArgoCD namespace exists"
if kubectl get namespace argocd >/dev/null 2>&1; then
pass
else
skip "ArgoCD not installed"
return 0
fi
# Check ArgoCD pods
print_check "ArgoCD pods running"
NOT_RUNNING=$(kubectl get pods -n argocd -o jsonpath='{.items[?(@.status.phase!="Running")].metadata.name}' 2>/dev/null)
if [ -z "$NOT_RUNNING" ]; then
pass
else
fail "Pods not running: $NOT_RUNNING"
fi
# Check ArgoCD CLI
print_check "ArgoCD CLI available"
if command_exists argocd; then
pass
ARGOCD_VERSION=$(argocd version --client --short 2>/dev/null || echo "unknown")
echo -e " ${BLUE}→ CLI Version: ${ARGOCD_VERSION}${NC}"
else
warn "argocd CLI not installed"
fi
# Check ArgoCD server version
print_check "ArgoCD server version"
if SERVER_VERSION=$(kubectl get deployment argocd-server -n argocd -o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null); then
pass
echo -e " ${BLUE}→ Server: ${SERVER_VERSION}${NC}"
else
warn "Could not determine server version"
fi
# Check Applications status
print_check "Application health"
if command_exists argocd && argocd app list >/dev/null 2>&1; then
DEGRADED=$(argocd app list -o json 2>/dev/null | jq -r '.[] | select(.status.health.status != "Healthy") | .metadata.name' 2>/dev/null || true)
if [ -z "$DEGRADED" ]; then
pass
APP_COUNT=$(argocd app list -o json 2>/dev/null | jq length 2>/dev/null || echo "?")
echo -e " ${BLUE}→ All ${APP_COUNT} applications healthy${NC}"
else
warn "Degraded apps: $DEGRADED"
fi
else
skip "Cannot check apps (not logged in or CLI unavailable)"
fi
# Check for OutOfSync applications
print_check "Application sync status"
if command_exists argocd && argocd app list >/dev/null 2>&1; then
OUTOFSYNC=$(argocd app list -o json 2>/dev/null | jq -r '.[] | select(.status.sync.status != "Synced") | .metadata.name' 2>/dev/null || true)
if [ -z "$OUTOFSYNC" ]; then
pass
else
warn "OutOfSync apps: $OUTOFSYNC"
fi
else
skip "Cannot check sync status"
fi
# Check repo server cache
print_check "Repository server health"
if kubectl exec -n argocd deploy/argocd-repo-server -- curl -s localhost:8084/healthz >/dev/null 2>&1; then
pass
else
warn "Cannot verify repo-server health"
fi
}
# ============================================
# FLUX CHECKS
# ============================================
check_flux() {
print_header "FLUX HEALTH"
# Check if Flux is installed
print_check "Flux namespace exists"
if kubectl get namespace flux-system >/dev/null 2>&1; then
pass
else
skip "Flux not installed"
return 0
fi
# Check Flux CLI
print_check "Flux CLI available"
if command_exists flux; then
pass
FLUX_VERSION=$(flux version --client 2>/dev/null | head -1 || echo "unknown")
echo -e " ${BLUE}→ CLI: ${FLUX_VERSION}${NC}"
else
warn "flux CLI not installed"
fi
# Check Flux components
print_check "Flux controllers running"
if command_exists flux; then
FLUX_STATUS=$(flux check 2>&1 || true)
if echo "$FLUX_STATUS" | grep -q "all checks passed"; then
pass
else
warn "Some Flux checks failed"
echo "$FLUX_STATUS" | head -5 | sed 's/^/ /'
fi
else
# Fallback to kubectl check
NOT_RUNNING=$(kubectl get pods -n flux-system -o jsonpath='{.items[?(@.status.phase!="Running")].metadata.name}' 2>/dev/null)
if [ -z "$NOT_RUNNING" ]; then
pass
else
fail "Pods not running: $NOT_RUNNING"
fi
fi
# Check Kustomizations
print_check "Kustomization reconciliation"
if kubectl get kustomizations.kustomize.toolkit.fluxcd.io -A >/dev/null 2>&1; then
FAILED_KS=$(kubectl get kustomizations.kustomize.toolkit.fluxcd.io -A -o json 2>/dev/null | \
jq -r '.items[] | select(.status.conditions[-1].status != "True") | .metadata.namespace + "/" + .metadata.name' 2>/dev/null || true)
if [ -z "$FAILED_KS" ]; then
pass
else
warn "Failed Kustomizations: $FAILED_KS"
fi
else
skip "No Kustomizations found"
fi
# Check Git sources
print_check "Git sources ready"
if kubectl get gitrepositories.source.toolkit.fluxcd.io -A >/dev/null 2>&1; then
FAILED_GIT=$(kubectl get gitrepositories.source.toolkit.fluxcd.io -A -o json 2>/dev/null | \
jq -r '.items[] | select(.status.conditions[-1].status != "True") | .metadata.namespace + "/" + .metadata.name' 2>/dev/null || true)
if [ -z "$FAILED_GIT" ]; then
pass
else
warn "Failed Git sources: $FAILED_GIT"
fi
else
skip "No GitRepositories found"
fi
}
# ============================================
# MANIFEST VALIDATION
# ============================================
validate_manifests() {
local MANIFEST_PATH="${1:-.}"
print_header "MANIFEST VALIDATION"
print_check "Manifest path exists"
if [ -d "$MANIFEST_PATH" ]; then
pass
echo -e " ${BLUE}→ Path: ${MANIFEST_PATH}${NC}"
else
fail "Path does not exist: $MANIFEST_PATH"
return 1
fi
# Check for kustomization.yaml
print_check "Kustomize structure"
if find "$MANIFEST_PATH" -name "kustomization.yaml" -o -name "kustomization.yml" | grep -q .; then
pass
KUSTOMIZE_COUNT=$(find "$MANIFEST_PATH" -name "kustomization.yaml" -o -name "kustomization.yml" | wc -l | tr -d ' ')
echo -e " ${BLUE}→ Found ${KUSTOMIZE_COUNT} kustomization files${NC}"
else
skip "No kustomization files found"
fi
# Validate kustomize build
if command_exists kustomize; then
print_check "Kustomize build validation"
local BUILD_FAILED=false
while IFS= read -r -d '' KS_FILE; do
KS_DIR=$(dirname "$KS_FILE")
if ! kustomize build "$KS_DIR" >/dev/null 2>&1; then
fail "Kustomize build failed for: $KS_DIR"
kustomize build "$KS_DIR" 2>&1 | head -5 | sed 's/^/ /'
BUILD_FAILED=true
fi
done < <(find "$MANIFEST_PATH" \( -name "kustomization.yaml" -o -name "kustomization.yml" \) -print0 2>/dev/null)
if [ "$BUILD_FAILED" = false ]; then
pass
fi
else
skip "kustomize CLI not installed"
fi
# Check for mutable image tags
print_check "Image tag immutability"
MUTABLE_TAGS=$(grep -rh "image:" "$MANIFEST_PATH" 2>/dev/null | grep -E ":(latest|dev|staging|master|main)$" || true)
if [ -z "$MUTABLE_TAGS" ]; then
pass
else
warn "Mutable image tags found"
echo "$MUTABLE_TAGS" | head -3 | sed 's/^/ /'
fi
# Check for secrets in plain text
print_check "No plaintext secrets"
SECRETS_IN_GIT=$(grep -rl "kind: Secret" "$MANIFEST_PATH" 2>/dev/null | \
xargs -I {} grep -l "^ [a-zA-Z]*:" {} 2>/dev/null | \
grep -v "SealedSecret\|ExternalSecret" || true)
if [ -z "$SECRETS_IN_GIT" ]; then
pass
else
warn "Plain secrets found (use SealedSecrets or ExternalSecrets)"
echo "$SECRETS_IN_GIT" | head -3 | sed 's/^/ /'
fi
# Validate Helm charts if present
if command_exists helm; then
print_check "Helm chart validation"
CHARTS=$(find "$MANIFEST_PATH" -name "Chart.yaml" 2>/dev/null || true)
if [ -n "$CHARTS" ]; then
for CHART in $CHARTS; do
CHART_DIR=$(dirname "$CHART")
if helm lint "$CHART_DIR" >/dev/null 2>&1; then
: # success
else
fail "Helm lint failed for: $CHART_DIR"
fi
done
pass
else
skip "No Helm charts found"
fi
fi
}
# ============================================
# GITOPS BEST PRACTICES
# ============================================
check_best_practices() {
print_header "GITOPS BEST PRACTICES"
# Check for automated sync with self-heal
print_check "Self-healing enabled"
if command_exists argocd && argocd app list >/dev/null 2>&1; then
NO_SELFHEAL=$(argocd app list -o json 2>/dev/null | \
jq -r '.[] | select(.spec.syncPolicy.automated.selfHeal != true) | .metadata.name' 2>/dev/null || true)
if [ -z "$NO_SELFHEAL" ]; then
pass
else
warn "Apps without self-heal: $(echo "$NO_SELFHEAL" | wc -l | tr -d ' ')"
fi
else
skip "Cannot check ArgoCD apps"
fi
# Check for prune enabled
print_check "Prune enabled"
if command_exists argocd && argocd app list >/dev/null 2>&1; then
NO_PRUNE=$(argocd app list -o json 2>/dev/null | \
jq -r '.[] | select(.spec.syncPolicy.automated.prune != true) | .metadata.name' 2>/dev/null || true)
if [ -z "$NO_PRUNE" ]; then
pass
else
warn "Apps without prune: $(echo "$NO_PRUNE" | wc -l | tr -d ' ')"
fi
else
skip "Cannot check ArgoCD apps"
fi
# Check for sync windows in production
print_check "Sync windows configured"
if kubectl get appprojects.argoproj.io -n argocd -o json 2>/dev/null | jq -e '.items[] | select(.metadata.name == "production") | .spec.syncWindows' >/dev/null 2>&1; then
pass
else
warn "No sync windows on production project"
fi
}
# ============================================
# SUMMARY
# ============================================
print_summary() {
print_header "SUMMARY"
echo -e " ${GREEN}Passed:${NC} $PASSED"
echo -e " ${RED}Failed:${NC} $FAILED"
echo -e " ${YELLOW}Warnings:${NC} $WARNINGS"
echo ""
if [ $FAILED -gt 0 ]; then
echo -e " ${RED}Status: UNHEALTHY - Fix failed checks above${NC}"
exit 1
elif [ $WARNINGS -gt 0 ]; then
echo -e " ${YELLOW}Status: WARNING - Review warnings above${NC}"
exit 0
else
echo -e " ${GREEN}Status: HEALTHY - All checks passed!${NC}"
exit 0
fi
}
# ============================================
# MAIN
# ============================================
main() {
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ GitOps Health Check ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
case "${1:-all}" in
--argocd)
check_cluster_connectivity
check_argocd
;;
--flux)
check_cluster_connectivity
check_flux
;;
--manifests)
validate_manifests "${2:-.}"
;;
--practices)
check_cluster_connectivity
check_best_practices
;;
all|*)
check_cluster_connectivity
check_argocd
check_flux
check_best_practices
;;
esac
print_summary
}
main "$@"
# yamllint disable rule:document-start rule:comments-indentation
# ArgoCD Application Template
# Complete example with all common configurations
#
# Usage:
# 1. Copy this file
# 2. Replace placeholders (marked with <...>)
# 3. Apply to ArgoCD namespace or commit to Git
#
# Reference: https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
# Application name - must be unique within ArgoCD namespace
name: <app-name>
namespace: argocd
# Finalizer ensures resources are deleted when Application is deleted
# Remove if you want to keep resources after Application deletion
finalizers:
- resources-finalizer.argocd.argoproj.io
# Labels for organization and filtering
labels:
app.kubernetes.io/name: <app-name>
app.kubernetes.io/part-of: <project-name>
environment: <dev|staging|production>
team: <team-name>
# Annotations for integrations
annotations:
# Notifications (if configured)
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: <channel>
notifications.argoproj.io/subscribe.on-health-degraded.slack: <channel>
# Kargo authorization (if using Kargo for promotions)
# kargo.akuity.io/authorized-stage: "<project>:<stage>"
spec:
# Project defines RBAC and allowed resources
# Use 'default' for simple setups or create dedicated project
project: default
# ============================================
# SOURCE CONFIGURATION
# ============================================
# Single source (simple)
source:
# Git repository URL
repoURL: https://github.com/<org>/<repo>.git
# Branch, tag, or commit SHA
targetRevision: HEAD # or 'main', 'v1.0.0', 'abc123'
# Path to manifests within repository
path: manifests/<app-name>
# --- KUSTOMIZE OPTIONS (if using Kustomize) ---
# kustomize:
# namePrefix: <prefix>-
# nameSuffix: -<suffix>
# commonLabels:
# app: <app-name>
# commonAnnotations:
# team: <team-name>
# images:
# - <old-image>=<new-image>:<tag>
# --- HELM OPTIONS (if using Helm) ---
# helm:
# releaseName: <release-name>
# valueFiles:
# - values.yaml
# - values-<env>.yaml
# parameters:
# - name: image.tag
# value: <version>
# - name: replicaCount
# value: "3"
# # For sensitive values, use valueFiles from a Secret
# # valuesObject can be used for inline values
# Multi-source (advanced - Helm with external values)
# sources:
# - repoURL: https://charts.bitnami.com/bitnami
# chart: nginx
# targetRevision: 15.0.0
# helm:
# valueFiles:
# - $values/<env>/nginx/values.yaml
# - repoURL: https://github.com/<org>/helm-values.git
# targetRevision: main
# ref: values
# ============================================
# DESTINATION CONFIGURATION
# ============================================
destination:
# Target cluster (use cluster name or URL)
# For in-cluster: https://kubernetes.default.svc
server: https://kubernetes.default.svc
# OR use cluster name registered in ArgoCD:
# name: production-cluster
# Target namespace (will be created if CreateNamespace=true)
namespace: <target-namespace>
# ============================================
# SYNC POLICY
# ============================================
syncPolicy:
# Automated sync (recommended for non-production)
automated:
# Automatically delete resources not in Git
prune: true
# Automatically revert manual changes
selfHeal: true
# Allow sync when only app spec changes (not desired state)
allowEmpty: false
# Sync options
syncOptions:
# Create namespace if it doesn't exist
- CreateNamespace=true
# Use server-side apply (better for CRDs)
- ServerSideApply=true
# Prune resources after sync completes
- PruneLast=true
# Only sync resources that are out of sync (performance)
- ApplyOutOfSyncOnly=true
# Respect ignoreDifferences during sync
- RespectIgnoreDifferences=true
# Skip validation (use with caution)
# - Validate=false
# Retry policy for failed syncs
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# ============================================
# IGNORE DIFFERENCES
# ============================================
# Ignore specific fields that are modified by controllers
ignoreDifferences:
# Ignore replicas managed by HPA
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
# Ignore auto-generated fields
- group: ""
kind: Service
jsonPointers:
- /spec/clusterIP
- /spec/clusterIPs
# Ignore webhook CA bundles (managed by cert-manager)
- group: admissionregistration.k8s.io
kind: MutatingWebhookConfiguration
jsonPointers:
- /webhooks/0/clientConfig/caBundle
# Ignore specific annotation
# - group: apps
# kind: Deployment
# jqPathExpressions:
# - .metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"]
# ============================================
# HEALTH CHECKS (Custom)
# ============================================
# Override default health assessment
# ignoreDifferences and health checks work together
# revisionHistoryLimit: 10 # Number of ReplicaSets to keep
---
# Production-ready example with manual sync
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: <app-name>-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: production # Use dedicated project with restrictions
source:
repoURL: https://github.com/<org>/<repo>.git
targetRevision: v1.0.0 # Pin to specific version
path: manifests/<app-name>/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: <app-name>-prod
# Manual sync for production (no automated)
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- PruneLast=true
# Note: No 'automated' block - requires manual sync
# yamllint disable rule:document-start rule:quoted-strings rule:comments-indentation
# ArgoCD ApplicationSet Templates
# Generates multiple Applications from a single template
#
# Usage:
# 1. Choose the generator pattern that fits your use case
# 2. Replace placeholders (marked with <...>)
# 3. Apply to ArgoCD namespace
#
# Reference: https://argo-cd.readthedocs.io/en/stable/user-guide/applicationset/
---
# ============================================
# PATTERN 1: LIST GENERATOR
# Deploy same app to multiple environments
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: <app-name>-environments
namespace: argocd
spec:
generators:
- list:
elements:
- env: dev
cluster: https://dev-cluster.example.com
namespace: <app-name>-dev
values_repo: dev
- env: staging
cluster: https://staging-cluster.example.com
namespace: <app-name>-staging
values_repo: staging
- env: production
cluster: https://prod-cluster.example.com
namespace: <app-name>-prod
values_repo: production
template:
metadata:
name: '<app-name>-{{env}}'
labels:
environment: '{{env}}'
spec:
project: '{{env}}'
# Multi-source: Helm chart + environment values
sources:
- repoURL: https://charts.example.com
chart: <chart-name>
targetRevision: <chart-version>
helm:
valueFiles:
- $values/{{values_repo}}/values.yaml
- repoURL: https://github.com/<org>/helm-values.git
targetRevision: main
ref: values
destination:
server: '{{cluster}}'
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---
# ============================================
# PATTERN 2: GIT DIRECTORY GENERATOR
# Auto-discover apps from Git repository structure
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: apps-discovery
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/<org>/gitops-repo.git
revision: HEAD
directories:
# Include all directories under apps/
- path: apps/*
# Exclude specific directories
- path: apps/excluded-app
exclude: true
template:
metadata:
# {{path.basename}} = directory name (e.g., "frontend", "backend")
name: '{{path.basename}}'
spec:
project: default
source:
repoURL: https://github.com/<org>/gitops-repo.git
targetRevision: HEAD
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---
# ============================================
# PATTERN 3: CLUSTER GENERATOR
# Deploy to all registered clusters
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- clusters:
# Select clusters by label
selector:
matchLabels:
environment: production
# Or match all clusters:
# selector: {}
template:
metadata:
name: 'monitoring-{{name}}'
spec:
project: infrastructure
source:
repoURL: https://github.com/<org>/cluster-addons.git
targetRevision: HEAD
path: monitoring
destination:
# {{server}} = cluster API URL
# {{name}} = cluster name
server: '{{server}}'
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
---
# ============================================
# PATTERN 4: MATRIX GENERATOR
# Combine two generators (e.g., clusters x apps)
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: platform-apps
namespace: argocd
spec:
generators:
- matrix:
generators:
# Generator 1: All production clusters
- clusters:
selector:
matchLabels:
environment: production
# Generator 2: All apps in apps/ directory
- git:
repoURL: https://github.com/<org>/platform-apps.git
revision: HEAD
directories:
- path: apps/*
template:
metadata:
# Combination of cluster name and app name
name: '{{name}}-{{path.basename}}'
spec:
project: platform
source:
repoURL: https://github.com/<org>/platform-apps.git
targetRevision: HEAD
path: '{{path}}'
destination:
server: '{{server}}'
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
---
# ============================================
# PATTERN 5: PULL REQUEST GENERATOR
# Deploy preview environments for PRs
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: pr-previews
namespace: argocd
spec:
generators:
- pullRequest:
github:
owner: <org>
repo: <repo>
tokenRef:
secretName: github-token
key: token
labels:
- preview
requeueAfterSeconds: 60
template:
metadata:
name: 'preview-{{branch_slug}}'
labels:
preview: "true"
pr: '{{number}}'
spec:
project: previews
source:
repoURL: 'https://github.com/<org>/<repo>.git'
targetRevision: '{{head_sha}}'
path: manifests
kustomize:
nameSuffix: '-pr-{{number}}'
destination:
server: https://kubernetes.default.svc
namespace: 'preview-{{number}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---
# ============================================
# PATTERN 6: MERGE GENERATOR
# Combine generators with override logic
# ============================================
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: team-apps
namespace: argocd
spec:
generators:
- merge:
mergeKeys:
- env
generators:
# Base configuration from list
- list:
elements:
- env: dev
replicas: "1"
autosync: "true"
- env: staging
replicas: "2"
autosync: "true"
- env: production
replicas: "3"
autosync: "false"
# Override specific environments
- list:
elements:
- env: production
cluster: https://prod.example.com
template:
metadata:
name: 'myapp-{{env}}'
spec:
project: '{{env}}'
source:
repoURL: https://github.com/<org>/myapp.git
targetRevision: HEAD
path: overlays/{{env}}
kustomize:
images:
- myapp:v1.0.0
destination:
server: '{{cluster}}'
namespace: myapp
# yamllint disable rule:document-start rule:quoted-strings rule:comments-indentation
# Kustomize Templates for GitOps
# Overlay-based configuration management
#
# Directory Structure:
# manifests/
# ├── base/ # Shared base configuration
# │ ├── kustomization.yaml
# │ ├── deployment.yaml
# │ ├── service.yaml
# │ └── configmap.yaml
# └── overlays/
# ├── dev/
# │ └── kustomization.yaml
# ├── staging/
# │ └── kustomization.yaml
# └── production/
# └── kustomization.yaml
---
# ============================================
# BASE KUSTOMIZATION
# manifests/base/kustomization.yaml
# ============================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Metadata applied to all resources
metadata:
name: <app-name>-base
# Common labels applied to all resources
commonLabels:
app.kubernetes.io/name: <app-name>
app.kubernetes.io/managed-by: kustomize
# Common annotations
commonAnnotations:
app.kubernetes.io/part-of: <project-name>
# Resources to include
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
- serviceaccount.yaml
# - ingress.yaml
# - hpa.yaml
# - pdb.yaml
# ConfigMap generator (creates ConfigMap from files/literals)
# configMapGenerator:
# - name: app-config
# files:
# - config.json
# literals:
# - LOG_LEVEL=info
# Secret generator (creates Secret from files/literals)
# secretGenerator:
# - name: app-secrets
# files:
# - secrets/api-key.txt
# type: Opaque
---
# ============================================
# DEVELOPMENT OVERLAY
# manifests/overlays/dev/kustomization.yaml
# ============================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Reference the base
resources:
- ../../base
# Namespace for all resources
namespace: <app-name>-dev
# Name prefix/suffix
namePrefix: dev-
# nameSuffix: -dev
# Additional labels for this environment
commonLabels:
environment: development
# Image override
images:
- name: <app-name>
newName: <registry>/<app-name>
newTag: latest # Dev can use latest
# Replica count override
replicas:
- name: <app-name>
count: 1
# Resource patches (strategic merge)
patches:
# Reduce resources for dev
- target:
kind: Deployment
name: <app-name>
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources
value:
limits:
cpu: "200m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
# ConfigMap patches
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=debug
- ENV=development
---
# ============================================
# STAGING OVERLAY
# manifests/overlays/staging/kustomization.yaml
# ============================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: <app-name>-staging
namePrefix: stg-
commonLabels:
environment: staging
images:
- name: <app-name>
newName: <registry>/<app-name>
newTag: v1.0.0-rc1 # Release candidate
replicas:
- name: <app-name>
count: 2
patches:
# Enable spot tolerations for staging
- target:
kind: Deployment
name: <app-name>
patch: |-
- op: add
path: /spec/template/spec/tolerations
value:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
- op: add
path: /spec/template/spec/affinity
value:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: "kubernetes.azure.com/scalesetpriority"
operator: In
values:
- "spot"
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=info
- ENV=staging
---
# ============================================
# PRODUCTION OVERLAY
# manifests/overlays/production/kustomization.yaml
# ============================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# Production-specific resources
- pdb.yaml # Pod Disruption Budget
- hpa.yaml # Horizontal Pod Autoscaler
- network-policy.yaml
namespace: <app-name>-prod
namePrefix: prod-
commonLabels:
environment: production
commonAnnotations:
owner: platform-team
cost-center: engineering
# Pin to specific immutable tag
images:
- name: <app-name>
newName: <registry>/<app-name>
# Use digest for production
# digest: sha256:abc123...
newTag: v1.0.0
replicas:
- name: <app-name>
count: 3
patches:
# Production-grade resources
- target:
kind: Deployment
name: <app-name>
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources
value:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
- op: add
path: /spec/template/spec/topologySpreadConstraints
value:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: <app-name>
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=warn
- ENV=production
---
# ============================================
# SAMPLE BASE DEPLOYMENT
# manifests/base/deployment.yaml
# ============================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: <app-name>
spec:
selector:
matchLabels:
app.kubernetes.io/name: <app-name>
template:
metadata:
labels:
app.kubernetes.io/name: <app-name>
spec:
serviceAccountName: <app-name>
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: <app-name>
image: <app-name> # Replaced by kustomize
ports:
- name: http
containerPort: 8080
protocol: TCP
envFrom:
- configMapRef:
name: app-config
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
---
# ============================================
# SAMPLE PRODUCTION PDB
# manifests/overlays/production/pdb.yaml
# ============================================
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: <app-name>
spec:
minAvailable: 2 # Or use maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: <app-name>
---
# ============================================
# SAMPLE PRODUCTION HPA
# manifests/overlays/production/hpa.yaml
# ============================================
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: <app-name>
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: <app-name>
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max