blog.maisumvictor.dev — zsh
GitOps with FluxCD: A Practical Guide
> Published:
GitOps changed how we deploy to Kubernetes. Instead of pushing changes, we pull them from Git. FluxCD is the engine that makes this happen.
What is GitOps?
GitOps has four key principles:
- Declarative: System state defined in Git
- Versioned: All changes are versioned and auditable
- Pulled: Agents pull changes automatically
- Continuously Reconciled: Drift is automatically corrected
Installing FluxCD
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Verify installation
flux --version
# Check cluster compatibility
flux check --pre
Bootstrapping Flux
# Set your GitHub credentials
export GITHUB_TOKEN=<your-token>
export GITHUB_USER=maisumvictor
# Bootstrap Flux in your cluster
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=flux-infra \
--branch=main \
--path=./clusters/production \
--personal
This creates a flux-infra repository with the following structure:
flux-infra/
├── clusters/
│ └── production/
│ ├── flux-system/ # Core Flux components
│ ├── infrastructure.yaml # Infrastructure apps
│ └── apps.yaml # Application deployments
├── infrastructure/
│ ├── nginx-ingress/
│ ├── cert-manager/
│ └── monitoring/
└── apps/
├── base/
└── production/
The GitOps Workflow
1. Source Configuration
# clusters/production/flux-system/gotk-sync.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: main
secretRef:
name: flux-system
url: https://github.com/maisumvictor/flux-infra
2. Kustomization for Infrastructure
# clusters/production/infrastructure.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 1h
retryInterval: 1m
path: ./infrastructure/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: cert-manager
namespace: cert-manager
timeout: 5m
3. Application Deployment
# clusters/production/apps.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m
path: ./apps/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: infrastructure
Helm Releases with Flux
# infrastructure/nginx-ingress/release.yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx-ingress
namespace: ingress-nginx
spec:
interval: 5m
chart:
spec:
chart: ingress-nginx
version: "4.8.x"
sourceRef:
kind: HelmRepository
name: ingress-nginx
namespace: flux-system
interval: 1m
values:
controller:
replicaCount: 2
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
metrics:
enabled: true
serviceMonitor:
enabled: true
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "10254"
Image Automation
Automatically deploy new container images:
# apps/production/image-policy.yaml
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: ghcr.io/maisumvictor/myapp
interval: 1m
secretRef:
name: ghcr-auth
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: myapp
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
semver:
range: "1.x.x"
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: myapp
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: flux-system
git:
checkout:
ref:
branch: main
commit:
author:
name: Flux Bot
email: [email protected]
messageTemplate: |
Automated image update
Images:
{{ range .Updated.Images -}}
- {{.}}
{{ end }}
signingKey:
secretRef:
name: flux-gpg-signing-key
push:
branch: main
policy:
automerge: true
Monitoring with Prometheus
# infrastructure/monitoring/prometheus.yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: kube-prometheus-stack
namespace: monitoring
spec:
interval: 5m
chart:
spec:
chart: kube-prometheus-stack
version: "55.x.x"
sourceRef:
kind: HelmRepository
name: prometheus-community
namespace: flux-system
values:
prometheus:
prometheusSpec:
retention: 30d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 50Gi
grafana:
enabled: true
adminPassword: "changeme"
ingress:
enabled: true
hosts:
- grafana.maisumvictor.dev
Alerting with Slack
# infrastructure/monitoring/alerts.yaml
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: alerts
secretRef:
name: slack-webhook
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: flux-alerts
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: GitRepository
name: '*'
- kind: Kustomization
name: '*'
- kind: HelmRelease
name: '*'
Common Commands
# Check Flux status
flux get all
# Check specific kustomization
flux get kustomizations --watch
# Suspend/Resume reconciliation
flux suspend kustomization apps
flux resume kustomization apps
# Force reconciliation
flux reconcile kustomization apps --with-source
# Check logs
flux logs --level=error
# Export current state
flux export all > backup.yaml
Key Takeaways
- Git is the single source of truth — All changes go through PRs
- Flux handles the complexity — You define what, Flux handles how
- Drift detection is automatic — Manual changes are reverted
- Rollback is just a git revert — Simple and familiar
The complete Flux configuration is in my flux-infra repository.