GitOps
GitOps Principles
- Declarative - Entire system described declaratively
- Versioned and immutable - Desired state stored in Git
- Pulled automatically - Agents pull state from Git
- Continuously reconciled - Agents ensure actual matches desired
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
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
# Install CLI
brew install argocd
# Login
argocd login localhost:8080 --username admin --password <password>
# Access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10
ArgoCD ApplicationSet
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-environments
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: dev
namespace: development
revision: develop
- cluster: staging
namespace: staging
revision: main
- cluster: prod
namespace: production
revision: main
template:
metadata:
name: 'myapp-{{cluster}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-manifests.git
targetRevision: '{{revision}}'
path: 'overlays/{{cluster}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
ArgoCD with Helm
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-helm
namespace: argocd
spec:
project: default
source:
repoURL: https://charts.example.com
chart: myapp
targetRevision: 1.2.0
helm:
releaseName: myapp
valueFiles:
- values-production.yaml
values: |
replicaCount: 5
image:
tag: v2.0.0
parameters:
- name: service.type
value: LoadBalancer
destination:
server: https://kubernetes.default.svc
namespace: production
ArgoCD Project
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
description: Production applications
sourceRepos:
- 'https://github.com/myorg/*'
- 'https://charts.example.com'
destinations:
- namespace: production
server: https://kubernetes.default.svc
- namespace: production-*
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceBlacklist:
- group: ''
kind: ResourceQuota
- group: ''
kind: LimitRange
roles:
- name: developer
description: Developer access
policies:
- p, proj:production:developer, applications, get, production/*, allow
- p, proj:production:developer, applications, sync, production/*, allow
groups:
- developers
Flux Installation
# Install Flux CLI
brew install fluxcd/tap/flux
# Check prerequisites
flux check --pre
# Bootstrap Flux (GitHub)
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--personal
# Bootstrap Flux (GitLab)
flux bootstrap gitlab \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production
Flux GitRepository
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: myapp
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/myapp-manifests
ref:
branch: main
secretRef:
name: github-credentials
ignore: |
# Exclude files
.git/
*.md
Flux Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 10m
targetNamespace: production
sourceRef:
kind: GitRepository
name: myapp
path: ./overlays/production
prune: true
timeout: 2m
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: myapp
namespace: production
postBuild:
substitute:
environment: production
replicas: "5"
substituteFrom:
- kind: ConfigMap
name: cluster-vars
Flux HelmRepository
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h
url: https://charts.bitnami.com/bitnami
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: redis
namespace: production
spec:
interval: 5m
chart:
spec:
chart: redis
version: '17.x'
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
values:
architecture: standalone
auth:
enabled: true
existingSecret: redis-credentials
master:
persistence:
size: 10Gi
Flux ImageUpdateAutomation
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: myregistry.io/myapp
interval: 1m
secretRef:
name: registry-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
name: myapp
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
semver:
range: '>=1.0.0'
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: myapp
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: myapp
git:
checkout:
ref:
branch: main
commit:
author:
email: [email protected]
name: fluxcdbot
messageTemplate: 'Update image to {{.NewTag}}'
push:
branch: main
update:
path: ./overlays/production
strategy: Setters
Progressive Delivery with Flagger
# Install Flagger
kubectl apply -k github.com/fluxcd/flagger/kustomize/istio
---
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: myapp
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
progressDeadlineSeconds: 600
service:
port: 80
targetPort: 8080
gateways:
- myapp-gateway
hosts:
- myapp.example.com
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.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://myapp-canary.production:80/"
Sealed Secrets
# Install controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Install kubeseal CLI
brew install kubeseal
# Create sealed secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=secret123 \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-db-credentials.yaml
# Apply sealed secret
kubectl apply -f sealed-db-credentials.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBy8h...encrypted...
password: AgCtr2...encrypted...
template:
type: Opaque
metadata:
labels:
app: myapp
SOPS with Age
# Install SOPS
brew install sops
# Generate age key
age-keygen -o age.agekey
# Create SOPS config
cat > .sops.yaml << EOF
creation_rules:
- path_regex: .*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1...publickey...
EOF
# Encrypt secret
sops --encrypt --in-place secrets.enc.yaml
# Configure Flux decryption
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey
# Flux Kustomization with SOPS
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
decryption:
provider: sops
secretRef:
name: sops-age
# ... rest of spec
Repository Strategies
Mono-repo
fleet-repo/
├── apps/
│ ├── myapp/
│ │ ├── base/
│ │ └── overlays/
│ └── another-app/
├── infrastructure/
│ ├── cert-manager/
│ └── ingress-nginx/
└── clusters/
├── dev/
├── staging/
└── production/
Multi-repo
# App repos (one per app)
myapp-manifests/
├── base/
└── overlays/
# Infrastructure repo
infrastructure/
├── cert-manager/
└── ingress-nginx/
# Fleet repo (references others)
fleet-infra/
├── apps.yaml # Points to app repos
└── infra.yaml # Points to infra repo
ArgoCD vs Flux Comparison
| Feature | ArgoCD | Flux |
|---|
| UI | Built-in web UI | Third-party (Weave GitOps) |
| Multi-tenancy | AppProject | Namespaced resources |
| Helm | Native support | HelmController |
| Image automation | ArgoCD Image Updater | Native ImagePolicy |
| Notifications | ArgoCD Notifications | Alerts/Receivers |
| RBAC | Built-in | Kubernetes RBAC |
| Architecture | Centralized | Distributed |
Best Practices
- Use separate repos for app code and manifests
- Protect main branch with required reviews
- Use sealed secrets or SOPS for sensitive data
- Enable auto-sync with prune for drift correction
- Set up notifications for sync failures
- Use ApplicationSets/Kustomizations for multi-environment
- Implement progressive delivery for safe rollouts
- Version your Helm charts semantically
- Keep manifests DRY with Kustomize overlays
- Monitor reconciliation metrics and alerts