GitOps
Master GitOps for declarative, Git-based continuous deployment. Learn to use ArgoCD for automated Kubernetes deployments with Git as the single source of truth.
What is GitOps?
GitOps is a modern approach to continuous deployment where the desired state of your infrastructure and applications is defined in Git, and automated processes ensure the actual state matches the desired state.
Core Principles
Git as Single Source of Truth
- All infrastructure and application configuration in Git
- Version controlled
- Auditable history
- Declarative definitions
Automated Synchronization
- Continuous reconciliation
- Automated deployment
- Self-healing systems
- Drift detection and correction
Pull-Based Deployment
- Agents pull changes from Git
- No external access to cluster required
- Enhanced security
- Better audit trail
Declarative Configuration
- Describe desired state
- System converges to that state
- Idempotent operations
- Predictable behavior
GitOps Workflow
┌─────────────────────────────────────────┐
│ Developer │
│ 1. Write code │
│ 2. Create Pull Request │
│ 3. Review and merge │
└──────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ Git Repository (Source of Truth) │
│ - Kubernetes manifests │
│ - Helm charts │
│ - Kustomize overlays │
└──────────────┬──────────────────────────┘
│ Pull changes
┌──────────────▼──────────────────────────┐
│ GitOps Operator (ArgoCD) │
│ 1. Monitor Git repository │
│ 2. Detect changes │
│ 3. Sync to cluster │
│ 4. Report status │
└──────────────┬──────────────────────────┘
│ Apply
┌──────────────▼──────────────────────────┐
│ Kubernetes Cluster │
│ - Deployments │
│ - Services │
│ - ConfigMaps │
│ - Secrets │
└─────────────────────────────────────────┘
ArgoCD
What is ArgoCD?
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It automatically synchronizes applications deployed in Kubernetes with their desired state defined in Git repositories.
Key Features
- Automated Deployment - Sync apps from Git to Kubernetes
- Multiple Sources - Helm, Kustomize, plain YAML
- Health Assessment - Monitor application health
- Rollback - Easy rollback to previous versions
- Multi-Cluster - Manage multiple clusters
- SSO Integration - OIDC, OAuth2, SAML
- RBAC - Fine-grained access control
- Web UI - Visual application management
Installing ArgoCD
Install in Kubernetes
# 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 pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=300s
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Access at https://localhost:8080
# Username: admin
# Password: (from above command)
Install ArgoCD CLI
# 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
rm argocd-linux-amd64
# macOS
brew install argocd
# Login via CLI
argocd login localhost:8080 --username admin --password <password>
# Change password
argocd account update-password
Creating Your First Application
Repository Structure
my-app-repo/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ └── kustomization.yaml
└── README.md
base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
replicas:
- name: myapp
count: 5
images:
- name: myapp
newTag: v1.0.0
Deploying with ArgoCD
Via UI
- Log in to ArgoCD UI
- Click "+ NEW APP"
- Fill in details:
- Application Name:
myapp - Project:
default - Sync Policy:
Automatic - Repository URL:
https://github.com/username/my-app-repo - Path:
overlays/production - Cluster:
https://kubernetes.default.svc - Namespace:
default
- Application Name:
- Click "CREATE"
Via CLI
# Create application
argocd app create myapp \
--repo https://github.com/username/my-app-repo \
--path overlays/production \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy automated \
--auto-prune \
--self-heal
# List applications
argocd app list
# Get application details
argocd app get myapp
# Sync application
argocd app sync myapp
# View application logs
argocd app logs myapp
# Delete application
argocd app delete myapp
Via YAML
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
# Source repository
source:
repoURL: https://github.com/username/my-app-repo
targetRevision: main
path: overlays/production
# Destination cluster
destination:
server: https://kubernetes.default.svc
namespace: default
# Sync policy
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
kubectl apply -f application.yaml
Auto Sync and Self-Healing
Automated Sync
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Revert manual changes
Behavior:
- ArgoCD automatically syncs when Git changes
- Detects drift and corrects it
- Removes resources not in Git (if
prune: true)
Manual Sync
syncPolicy: {} # No automated sync
Behavior:
- Manual approval required
- Better for production environments
- Use CLI or UI to sync
Multi-Environment Setup
Repository Structure
gitops-repo/
├── apps/
│ └── myapp/
│ ├── base/
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── production/
└── argocd-apps/
├── dev-myapp.yaml
├── staging-myapp.yaml
└── prod-myapp.yaml
argocd-apps/dev-myapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/username/gitops-repo
targetRevision: main
path: apps/myapp/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
argocd-apps/prod-myapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/username/gitops-repo
targetRevision: production # Different branch
path: apps/myapp/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: false # Manual approval for prod
syncOptions:
- CreateNamespace=true
App of Apps Pattern
Manage multiple applications with a single parent app.
apps/app-of-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-of-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/username/gitops-repo
targetRevision: main
path: argocd-apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
kubectl apply -f apps/app-of-apps.yaml
This creates all child applications defined in argocd-apps/.
Working with Helm
Helm Repository
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-ingress
namespace: argocd
spec:
project: default
source:
chart: nginx-ingress
repoURL: https://helm.nginx.com/stable
targetRevision: 0.17.0
helm:
values: |
controller:
replicaCount: 3
service:
type: LoadBalancer
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Helm Chart in Git
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/username/my-helm-chart
targetRevision: main
path: charts/myapp
helm:
valueFiles:
- values-production.yaml
parameters:
- name: image.tag
value: v1.0.0
- name: replicaCount
value: "5"
destination:
server: https://kubernetes.default.svc
namespace: production
Secrets Management
Sealed Secrets
# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml
# Install kubeseal CLI
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/kubeseal-0.18.0-linux-amd64.tar.gz
tar xfz kubeseal-0.18.0-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
Create sealed secret:
# Create regular secret
kubectl create secret generic db-credentials \
--from-literal=password=SuperSecret123! \
--dry-run=client -o yaml > secret.yaml
# Seal it
kubeseal -f secret.yaml -w sealed-secret.yaml
# Commit sealed-secret.yaml to Git (safe!)
git add sealed-secret.yaml
git commit -m "Add database credentials"
git push
sealed-secret.yaml (safe to commit):
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
Monitoring and Notifications
Prometheus Metrics
ArgoCD exposes metrics for Prometheus:
apiVersion: v1
kind: ServiceMonitor
metadata:
name: argocd-metrics
namespace: argocd
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-server-metrics
endpoints:
- port: metrics
Slack Notifications
# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
template.app-deployed: |
message: |
Application {{.app.metadata.name}} is now running version {{.app.status.sync.revision}}.
slack:
attachments: |
[{
"title": "{{ .app.metadata.name}}",
"title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
},
{
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}
]
}]
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-deployed]
Subscribe application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
annotations:
notifications.argoproj.io/subscribe.on-deployed.slack: my-channel
Best Practices
1. Separate App and Config Repos
app-repo/ # Application code
config-repo/ # Kubernetes manifests
2. Use Branches for Environments
main → development
staging → staging
production → production
3. Implement Progressive Delivery
1. Deploy to dev (auto-sync)
2. Run tests
3. Deploy to staging (auto-sync)
4. Manual approval
5. Deploy to production (manual sync)
4. Structure with Kustomize
base/ # Common resources
overlays/
dev/ # Dev-specific
staging/ # Staging-specific
production/ # Prod-specific
5. Use Projects for Multi-Tenancy
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-a
namespace: argocd
spec:
destinations:
- namespace: 'team-a-*'
server: https://kubernetes.default.svc
sourceRepos:
- https://github.com/company/team-a-apps
6. Enable RBAC
# argocd-rbac-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
p, role:org-admin, applications, *, */*, allow
p, role:org-admin, clusters, get, *, allow
g, team-a, role:org-admin
Troubleshooting
Application OutOfSync:
# Check diff
argocd app diff myapp
# View current vs desired state
kubectl get deployment myapp -o yaml
argocd app manifests myapp
Sync Fails:
# Check sync status
argocd app get myapp
# View logs
argocd app logs myapp --follow
# Manual sync with prune
argocd app sync myapp --prune
Health Check Failures:
# Custom health check
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
resource.customizations: |
argoproj.io/Application:
health.lua: |
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
if obj.status.health ~= nil then
hs.status = obj.status.health.status
if obj.status.health.message ~= nil then
hs.message = obj.status.health.message
end
end
end
return hs
Resources
Official Documentation
Tools
- ArgoCD - GitOps CD for Kubernetes
- Flux - Alternative GitOps tool
- Sealed Secrets - Encrypt Kubernetes secrets
Learning
- ArgoCD Getting Started
- Kustomize - Kubernetes config management
- Helm - Kubernetes package manager
Master GitOps with ArgoCD for reliable, automated Kubernetes deployments!