[22/24] G is for GitOps: Modern Deployment Workflows
This is Post #22 in the Kubernetes A-to-Z Series
Reading Order: Previous: Zero-Downtime Deployments | Next: Best Practices
Series Progress: 22/24 complete | Difficulty: Advanced | Time: 40 min | Part 6/6: Security & Production
GitOps has revolutionized how teams deploy and manage applications on Kubernetes. By using Git as the single source of truth for declarative infrastructure and applications, GitOps brings versioning, collaboration, and automation to Kubernetes deployments.
This comprehensive guide explores GitOps principles and compares the two leading GitOps tools: ArgoCD and FluxCD.
What is GitOps?
GitOps is an operational framework that applies DevOps best practices—like version control, collaboration, and CI/CD—to infrastructure automation.
Traditional Deployment:
┌──────────────────────────────────────────────┐
│ Developer → kubectl apply → Cluster │
│ - No audit trail │
│ - Manual changes │
│ - Configuration drift │
│ - Hard to rollback │
└──────────────────────────────────────────────┘
GitOps Deployment:
┌──────────────────────────────────────────────┐
│ Developer → Git Push → GitOps Operator │
│ ↓ │
│ Git Repository │
│ ↓ │
│ Automatic Sync │
│ ↓ │
│ Kubernetes Cluster │
│ - Full audit trail │
│ - Declarative │
│ - Self-healing │
│ - Easy rollback │
└──────────────────────────────────────────────┘
Core GitOps Principles
- Declarative: Entire system described declaratively
- Versioned: Canonical state versioned in Git
- Pulled Automatically: Software agents pull desired state
- Continuously Reconciled: Agents ensure correctness
GitOps Benefits
- Increased Velocity: Faster deployments through automation
- Improved Reliability: Declarative approach reduces errors
- Enhanced Security: No direct cluster access needed
- Better Collaboration: Git workflow for infrastructure
- Easy Rollback: Git history enables instant rollbacks
- Audit Trail: Every change tracked in Git
- Disaster Recovery: Cluster rebuilt from Git
ArgoCD vs FluxCD: Overview
ArgoCD
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes developed by Intuit.
Key Characteristics:
- User-friendly Web UI and CLI
- Multi-cluster management
- SSO integration
- RBAC support
- Application health assessment
- Rich visualization
FluxCD
FluxCD is a GitOps operator for Kubernetes that synchronizes cluster state with Git, part of the CNCF.
Key Characteristics:
- Lightweight and Kubernetes-native
- Modular architecture (Toolkit)
- Progressive delivery with Flagger
- Multi-tenancy support
- Notification system
- CLI-focused workflow
Feature Comparison
| Feature | ArgoCD | FluxCD |
|---|---|---|
| UI | Rich Web UI | No built-in UI |
| Architecture | Monolithic | Modular (Toolkit) |
| Helm Support | Native | Native |
| Kustomize | Native | Native |
| Multi-tenancy | Good | Excellent |
| SSO | Built-in | External |
| Progressive Delivery | Argo Rollouts (separate) | Flagger (integrated) |
| Notifications | Yes | Yes |
| Image Automation | Limited | Excellent |
| Webhook Support | Yes | Yes |
| Learning Curve | Moderate | Steeper |
Installing ArgoCD
Installation
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods to be ready
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Access UI at https://localhost:8080
# Username: admin
# Password: (from command above)
ArgoCD CLI Installation
# macOS
brew install argocd
# Linux
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
# Login via CLI
argocd login localhost:8080 --insecure
argocd account update-password
ArgoCD: Creating Applications
Application Manifest
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
namespace: argocd
spec:
project: default
# Source repository
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
# Destination cluster
destination:
server: https://kubernetes.default.svc
namespace: guestbook
# Sync policy
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Force sync when cluster state changes
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Helm Application
# helm-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-ingress
namespace: argocd
spec:
project: default
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.8.3
helm:
values: |
controller:
replicaCount: 3
service:
type: LoadBalancer
resources:
requests:
cpu: 100m
memory: 128Mi
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Kustomize Application
# kustomize-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
ArgoCD CLI Commands
# Application Management
argocd app create -f application.yaml
argocd app list
argocd app get guestbook
argocd app sync guestbook
argocd app delete guestbook
# Manual sync
argocd app sync guestbook
# Diff between Git and cluster
argocd app diff guestbook
# View history
argocd app history guestbook
# Rollback
argocd app rollback guestbook 1
# View logs
argocd app logs guestbook
# Repository Management
argocd repo add https://github.com/myorg/myrepo.git --username myuser --password mytoken
argocd repo list
# Cluster Management
argocd cluster add my-cluster
argocd cluster list
# Project Management
argocd proj create myproject
argocd proj list
Installing FluxCD
Installation
# Install Flux CLI
# macOS
brew install fluxcd/tap/flux
# Linux
curl -s https://fluxcd.io/install.sh | sudo bash
# Windows
choco install flux
# Verify installation
flux --version
# Check cluster prerequisites
flux check --pre
# Bootstrap Flux (GitHub example)
export GITHUB_TOKEN=<your-token>
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--personal
Bootstrap with GitLab
export GITLAB_TOKEN=<your-token>
flux bootstrap gitlab \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production
FluxCD: GitRepository and Kustomization
GitRepository Resource
# gitrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: webapp
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/webapp
ref:
branch: main
secretRef:
name: git-credentials
Kustomization Resource
# kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: webapp
namespace: flux-system
spec:
interval: 5m
path: ./deploy/production
prune: true
sourceRef:
kind: GitRepository
name: webapp
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: webapp
namespace: production
timeout: 2m
FluxCD: Helm Repositories and Releases
HelmRepository
# helmrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: nginx
namespace: flux-system
spec:
interval: 10m
url: https://kubernetes.github.io/ingress-nginx
HelmRelease
# helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx-ingress
namespace: flux-system
spec:
interval: 5m
chart:
spec:
chart: ingress-nginx
version: 4.8.3
sourceRef:
kind: HelmRepository
name: nginx
namespace: flux-system
values:
controller:
replicaCount: 3
service:
type: LoadBalancer
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
FluxCD CLI Commands
# Source Management
flux create source git webapp \
--url=https://github.com/myorg/webapp \
--branch=main \
--interval=1m
flux get sources git
flux reconcile source git webapp
# Kustomization Management
flux create kustomization webapp \
--source=webapp \
--path="./deploy/production" \
--prune=true \
--interval=5m
flux get kustomizations
flux reconcile kustomization webapp
# Helm Management
flux create source helm nginx \
--url=https://kubernetes.github.io/ingress-nginx \
--interval=10m
flux create helmrelease nginx-ingress \
--source=HelmRepository/nginx \
--chart=ingress-nginx \
--chart-version=4.8.3
flux get helmreleases
flux reconcile helmrelease nginx-ingress
# Image Automation
flux create image repository webapp \
--image=myregistry/webapp \
--interval=1m
flux create image policy webapp \
--image-ref=webapp \
--select-semver=">=1.0.0"
# Monitoring
flux logs
flux events
flux stats
Image Automation with FluxCD
FluxCD excels at automated image updates:
# imagerepository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: webapp
namespace: flux-system
spec:
image: myregistry/webapp
interval: 1m
---
# imagepolicy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: webapp
namespace: flux-system
spec:
imageRepositoryRef:
name: webapp
policy:
semver:
range: ">=1.0.0"
---
# imageupdateautomation.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: webapp
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: webapp
git:
checkout:
ref:
branch: main
commit:
author:
email: [email protected]
name: fluxcdbot
messageTemplate: "Update image to {{range .Updated.Images}}{{println .}}{{end}}"
push:
branch: main
update:
path: ./deploy/production
strategy: Setters
Update deployment with image marker:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
template:
spec:
containers:
- name: webapp
image: myregistry/webapp:1.0.0 # {"$imagepolicy": "flux-system:webapp"}
Multi-Environment Strategy
Repository Structure
fleet-infra/
├── base/
│ └── webapp/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
├── clusters/
│ ├── development/
│ │ ├── webapp-kustomization.yaml
│ │ └── flux-system/
│ ├── staging/
│ │ ├── webapp-kustomization.yaml
│ │ └── flux-system/
│ └── production/
│ ├── webapp-kustomization.yaml
│ └── flux-system/
└── environments/
├── development/
│ └── webapp/
│ ├── kustomization.yaml
│ └── patches.yaml
├── staging/
│ └── webapp/
│ ├── kustomization.yaml
│ └── patches.yaml
└── production/
└── webapp/
├── kustomization.yaml
└── patches.yaml
Environment-Specific Kustomization
# clusters/production/webapp-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: webapp
namespace: flux-system
spec:
interval: 5m
path: ./environments/production/webapp
prune: true
sourceRef:
kind: GitRepository
name: flux-system
validation: client
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: webapp
namespace: production
Progressive Delivery with Flagger
Flagger automates canary deployments:
# Install Flagger
flux create source helm flagger \
--url=https://flagger.app \
--interval=1h
flux create helmrelease flagger \
--source=HelmRepository/flagger \
--chart=flagger \
--chart-version=1.33.0 \
--namespace=flagger-system \
--create-target-namespace=true
Canary Release
# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: webapp
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: webapp
service:
port: 80
targetPort: 8080
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://webapp-canary.production/"
Security Best Practices
Sealed Secrets (FluxCD)
# Install Sealed Secrets controller
flux create source helm sealed-secrets \
--url=https://bitnami-labs.github.io/sealed-secrets
flux create helmrelease sealed-secrets \
--source=HelmRepository/sealed-secrets \
--chart=sealed-secrets
# Create sealed secret
echo -n mypassword | kubectl create secret generic db-password \
--dry-run=client \
--from-file=password=/dev/stdin \
-o yaml | \
kubeseal -o yaml > sealed-secret.yaml
RBAC Configuration (ArgoCD)
# argocd-rbac-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
p, role:developers, applications, get, */*, allow
p, role:developers, applications, sync, */*, allow
p, role:ops-team, applications, *, */*, allow
p, role:ops-team, clusters, *, *, allow
g, developers-group, role:developers
g, ops-group, role:ops-team
Monitoring and Notifications
ArgoCD Notifications
# argocd-notifications-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-deployed]
template.app-deployed: |
message: Application {{.app.metadata.name}} deployed successfully
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"color": "good",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}]
}]
FluxCD Notifications
# notification-provider.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: deployments
secretRef:
name: slack-webhook
---
# alert.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: webapp
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: webapp
- kind: HelmRelease
name: webapp
When to Choose ArgoCD vs FluxCD
Choose ArgoCD When:
✅ UI is important for team visibility ✅ SSO integration is required ✅ Multi-cluster management from single control plane ✅ Application health visualization is valuable ✅ Quick onboarding for teams new to GitOps ✅ Rich RBAC requirements
Choose FluxCD When:
✅ CLI-first workflow preferred ✅ Image automation is critical ✅ Multi-tenancy requirements ✅ Progressive delivery with Flagger ✅ Lightweight footprint desired ✅ CNCF ecosystem alignment important ✅ Modular architecture for customization
Best Practices
Repository Structure
gitops-repo/
├── apps/
│ ├── base/
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── prod/
├── infrastructure/
│ ├── controllers/
│ ├── sources/
│ └── configs/
└── clusters/
├── dev/
├── staging/
└── prod/
Security
- Use private repositories for sensitive configs
- Implement RBAC for repository access
- Encrypt secrets (Sealed Secrets, SOPS, External Secrets)
- Enable audit logging
- Use signed commits
Operations
- Monitor sync status and health
- Set up alerts for sync failures
- Use health checks for critical resources
- Implement proper retry and backoff strategies
- Test changes in lower environments first
Performance
- Use sync windows for controlled deployments
- Implement resource limits on GitOps operators
- Optimize webhook delivery
- Cache Git repositories appropriately
- Monitor resource utilization
Troubleshooting
ArgoCD
# Check application status
argocd app get myapp
# View sync errors
argocd app get myapp --show-operation
# Force refresh
argocd app get myapp --refresh
# Hard refresh
argocd app get myapp --hard-refresh
# Check controller logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller
FluxCD
# Check reconciliation status
flux get all
# View logs
flux logs --all-namespaces
# Suspend/resume reconciliation
flux suspend kustomization webapp
flux resume kustomization webapp
# Force reconciliation
flux reconcile kustomization webapp --with-source
# Check events
kubectl events -n flux-system
Migration Path
From Traditional CI/CD to GitOps
- Start small: Pick one non-critical application
- Structure repository: Organize manifests properly
- Deploy GitOps tool: Install ArgoCD or FluxCD
- Create application: Point to Git repository
- Enable automation: Turn on auto-sync
- Monitor: Watch for issues, iterate
- Expand: Gradually add more applications
Key Takeaways
- GitOps uses Git as single source of truth
- ArgoCD provides user-friendly UI and visualization
- FluxCD offers modular, lightweight architecture
- Both tools support Helm and Kustomize
- Progressive delivery available via Argo Rollouts or Flagger
- Security through RBAC, secrets management, and audit trails
- Choose based on team needs and workflow preferences
Resources for Further Learning
- GitOps Principles
- ArgoCD Documentation
- FluxCD Documentation
- Flagger Documentation
- CNCF GitOps Working Group
GitOps represents the future of Kubernetes deployments. Whether you choose ArgoCD for its rich UI and ease of use, or FluxCD for its modularity and GitOps purity, both tools will transform how you manage applications in Kubernetes.