07: GitOps & Flux

GitOps Definition

GitOps = Managing infrastructure through Git as the single source of truth.

Traditional (Push):
  Jenkins runs: helm upgrade my-app ./chart
      ↓
  Cluster gets updated

GitOps (Pull):
  Developer pushes config to Git
      ↓
  Flux watches Git (polling every 1 minute)
      ↓
  Flux detects change, auto-applies it
      ↓
  Cluster reconciles to match Git

Benefits:

  • ✅ Git = audit trail (who changed what, when)
  • ✅ Automatic drift detection (cluster != Git → Flux corrects)
  • ✅ Declarative (desired state in Git)
  • ✅ Easy rollback (revert Git commit)

Flux Architecture

graph LR A["GitHub
(source of truth)"] -->|polling
every 1 min| B["Flux Controller
(in cluster)"] B -->|reconcile| C["Kubernetes Cluster"] style A fill:#e3f2fd style B fill:#f3e5f5 style C fill:#e8f5e9

Components

Component Role
Source Controller Fetch configs from Git
Helm Controller Reconcile Helm releases
Kustomize Controller Manage Kustomize patches
Notification Controller Send alerts (Slack, etc.)

Setting Up Flux

# Install Flux CLI
brew install fluxcd/tap/flux

# Bootstrap Flux on cluster + GitHub (one command!)
flux bootstrap github \
  --owner=yourname \
  --repo=myapp-config \
  --branch=main \
  --path=./clusters/production \
  --personal
  # Creates repo, installs Flux, sets up sync

This creates:

myapp-config/ (GitHub repo)
├── clusters/
│   └── production/
│       ├── flux-system/           (Flux components)
│       └── releases.yaml          (HelmReleases)
└── .github/workflows/flux-sync.yml


HelmRelease CRD

Tell Flux which Helm chart to deploy.

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: api-server
  namespace: production
spec:
  interval: 5m  # Check Git every 5 minutes
  releaseName: api-server  # Helm release name

  chart:
    spec:
      chart: microservices
      version: "1.2.3"  # Specific version
      sourceRef:
        kind: HelmRepository
        name: myrepo

  values:
    replicaCount: 3
    image:
      tag: "1.0.0"
    autoscaling:
      enabled: true
      maxReplicas: 10

  postRenderers:  # Post-process before applying
  - kustomize:
      patchesStrategicMerge:
      - apiVersion: v1
        kind: Service
        metadata:
          name: api-server
        spec:
          type: LoadBalancer

GitRepository Source

Tell Flux where to fetch configs from.

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: my-app-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/yourname/myapp-config
  ref:
    branch: main
  secretRef:  # SSH key to access private repo
    name: flux-github-deploy-key

Helm Repository Source

Tell Flux where to find Helm charts.

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: myrepo-charts
  namespace: flux-system
spec:
  interval: 5m
  url: https://charts.example.com
  secretRef:
    name: helm-repo-creds  # If auth required

Drift Detection

If something manually changes the cluster (kubectl edit, etc.), Flux detects and corrects it.

Step 1: Intentional cluster change
  kubectl patch deployment api-server --type='json' \
    -p='[{"op": "replace", "path": "/spec/replicas", "value":1}]'

Step 2: Flux notices drift
  Flux controller reconciles HelmRelease

Step 3: Flux corrects it
  Deployment replicas = 3 (as defined in Git)
  Slack alert: "Corrected manual change to deployment"

Multi-Cluster GitOps

Deploy to east AND west clusters from one Git repo:

myapp-config/
├── clusters/
│   ├── east/
│   │   ├── flux-system/
│   │   └── releases.yaml      # HelmRelease for east
│   │       └── values: replicas: 5
│   │
│   └── west/
│       ├── flux-system/
│       └── releases.yaml      # HelmRelease for west
│           └── values: replicas: 3
└── apps/
    ├── api-service/
    │   ├── Chart.yaml
    │   └── templates/

Both clusters have:

flux bootstrap github --path=./clusters/east
flux bootstrap github --path=./clusters/west

Each cluster reconciles its own directory independently!


Notifications & Alerts

Alert when deployments fail or drift occurs.

apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
  name: deployment-alerts
  namespace: flux-system
spec:
  providerRef:
    name: slack-webhook
  suspend: false
  eventSeverity: error
  eventSources:
  - kind: HelmRelease
    name: '*'
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
  name: slack-webhook
  namespace: flux-system
spec:
  type: slack
  address: https://hooks.slack.com/services/YOUR/WEBHOOK/URL

Flux vs Jenkins (Push) vs GitOps

Aspect Jenkins (Push) Flux (Pull)
Deployment trigger Pipeline runs helm upgrade Flux detects Git change
Cluster drift No detection Automatic detection & correction
Audit trail Pipeline logs Git commit history
Rollback Manual; rerun pipeline git revert commit
Secrets Jenkins secrets store K8s Secrets (encrypted at rest)
Learn curve Medium Steep initially

Best Practices

Keep all config in Git

# BAD: Manual kubectl apply
kubectl apply -f manifest.yaml

# GOOD: Push to Git, let Flux apply
git push origin config-change

Use different branches for environments

main             → production cluster
staging          → staging cluster
feature/my-work  → review in branch cluster

Encrypt secrets in Git

Use SOPS or Sealed Secrets, not plaintext

Test before merging

Use preview/ephemeral clusters to test Git changes


Interview Questions

Q: What's the difference between Flux and Jenkins?

A: Jenkins = push-based CD (pipeline deploys). Flux = pull-based GitOps (cluster auto-syncs from Git). Flux adds drift detection and audit trail.

Q: How does Flux detect when to update?

A: Flux periodically polls Git (default 1 minute). If config changed, Flux applies it.

Q: Can you rollback with Flux?

A: Yes, revert the Git commit. Flux will detect the change and roll back the cluster.


Key Takeaways

GitOps = Git as source of truth
Flux auto-syncs cluster to match Git
HelmRelease declares desired Helm deployments
Drift detection = auto-correction of manual changes
Git commit history = audit trail
Pull model = cluster controls its own destiny


Next Steps