GitOps has matured from buzzword to standard practice. Combined with progressive delivery—canary deployments, feature flags, and automated rollbacks—it enables confident, frequent deployments.
Here’s how to implement GitOps with progressive delivery.
GitOps Fundamentals
Core Principles
gitops_principles:
declarative:
- Desired state described in git
- No imperative commands
- Complete system description
versioned:
- Git is source of truth
- Full history of changes
- Easy rollback (git revert)
pulled:
- Cluster pulls from git
- No external access to cluster
- More secure than push
reconciled:
- Continuous reconciliation
- Drift detection and correction
- Self-healing
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Git Repository │
│ (source of truth) │
└───────────────────────────┬─────────────────────────────────────┘
│ pull
▼
┌─────────────────────────────────────────────────────────────────┐
│ GitOps Operator │
│ (Argo CD / Flux) │
├─────────────────────────────────────────────────────────────────┤
│ - Watch git for changes │
│ - Compare desired vs actual │
│ - Sync (reconcile) │
│ - Report status │
└───────────────────────────┬─────────────────────────────────────┘
│ apply
▼
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
└─────────────────────────────────────────────────────────────────┘
Argo CD Setup
Installation
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
Application Definition
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app-config
targetRevision: HEAD
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Multi-Environment Setup
# Application per environment
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app
spec:
generators:
- list:
elements:
- env: staging
cluster: staging-cluster
- env: production
cluster: production-cluster
template:
metadata:
name: 'my-app-{{env}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app-config
targetRevision: HEAD
path: 'overlays/{{env}}'
destination:
server: '{{cluster}}'
namespace: my-app
Progressive Delivery
Argo Rollouts
Enhanced deployment strategies:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: myregistry/my-app:v2
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 5m }
- setWeight: 100
Canary with Analysis
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 2m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 100
canaryService: my-app-canary
stableService: my-app-stable
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.95
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
status=~"2.."
}[5m])) /
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[5m]))
Blue-Green Deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 3
strategy:
blueGreen:
activeService: my-app-active
previewService: my-app-preview
autoPromotionEnabled: false
prePromotionAnalysis:
templates:
- templateName: smoke-tests
postPromotionAnalysis:
templates:
- templateName: success-rate
Feature Flags Integration
With LaunchDarkly/Flagsmith
# Deployment with feature flag sidecar
spec:
containers:
- name: app
env:
- name: FEATURE_FLAG_SERVER
value: http://localhost:8080
- name: flag-relay
image: launchdarkly/ld-relay
env:
- name: LD_SDK_KEY
valueFrom:
secretKeyRef:
name: launchdarkly
key: sdk-key
Progressive Rollout with Flags
# Application code checks flag
feature_flags:
new_checkout_flow:
default: false
rules:
- percentage: 10 # 10% of users
value: true
- percentage: 50 # After validation
value: true
- percentage: 100 # Full rollout
value: true
Automated Rollbacks
On Analysis Failure
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 20
- pause: { duration: 5m }
- analysis:
templates:
- templateName: error-rate
args:
- name: threshold
value: "0.01" # 1% error rate
# Automatic rollback on analysis failure
abortScaleDownDelaySeconds: 30
Manual Rollback
# Argo Rollouts CLI
kubectl argo rollouts abort my-app
kubectl argo rollouts undo my-app
# Or through Argo CD
argocd app sync my-app --revision <previous-commit>
Repository Structure
Monorepo Pattern
config-repo/
├── apps/
│ ├── my-app/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── staging/
│ │ │ ├── kustomization.yaml
│ │ │ └── patch.yaml
│ │ └── production/
│ │ ├── kustomization.yaml
│ │ └── patch.yaml
│ └── other-app/
├── infrastructure/
│ ├── cert-manager/
│ ├── ingress-nginx/
│ └── monitoring/
└── argocd/
├── projects.yaml
└── applications.yaml
App-of-Apps Pattern
# Root application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
spec:
source:
path: argocd/applications
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
CI/CD Integration
Image Updater
# Argo CD Image Updater
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
annotations:
argocd-image-updater.argoproj.io/image-list: app=myregistry/my-app
argocd-image-updater.argoproj.io/app.update-strategy: semver
argocd-image-updater.argoproj.io/write-back-method: git
PR-Based Updates
# CI pipeline updates config repo
steps:
- name: Update image tag
run: |
cd config-repo
kustomize edit set image myregistry/my-app:${{ github.sha }}
git commit -am "Update my-app to ${{ github.sha }}"
git push
Monitoring and Notifications
Argo CD Notifications
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
data:
trigger.on-sync-succeeded: |
- description: Application synced successfully
send:
- app-sync-succeeded
when: app.status.operationState.phase in ['Succeeded']
template.app-sync-succeeded: |
message: |
{{.app.metadata.name}} synced successfully.
Revision: {{.app.status.sync.revision}}
service.slack: |
token: $slack-token
Metrics
# Key metrics to monitor
gitops_metrics:
- argocd_app_sync_status
- argocd_app_health_status
- rollout_phase
- analysis_run_metric_result
- deployment_update_total
Key Takeaways
- GitOps uses git as single source of truth; cluster pulls desired state
- Argo CD syncs git state to Kubernetes continuously with drift correction
- Progressive delivery (canary, blue-green) reduces deployment risk
- Argo Rollouts enables sophisticated deployment strategies with analysis
- Automated analysis compares metrics to thresholds; rollback on failure
- Feature flags complement progressive delivery for fine-grained control
- App-of-Apps pattern manages multiple applications declaratively
- Image updater automates image tag updates in git
- Monitor sync status, health, and rollout progress
- Combine GitOps discipline with progressive delivery confidence
GitOps provides the workflow. Progressive delivery provides the safety. Together, they enable frequent, confident deployments.