Introduction#
GitOps treats Git as the single source of truth for infrastructure and application state. Flux watches a Git repository and automatically applies changes to a Kubernetes cluster, replacing manual kubectl apply or helm upgrade commands. This post covers Flux v2 setup and practical patterns.
Core Concepts#
Source Controller: watches Git repositories, Helm repositories, and OCI registries for changes.
Kustomize Controller: applies Kustomize configurations from sources.
Helm Controller: applies HelmReleases from Helm charts in sources.
Notification Controller: sends alerts to Slack, Teams, or webhooks.
Image Automation Controller: automatically updates image tags in Git when new images are pushed.
Installing Flux#
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Bootstrap: installs Flux into the cluster and creates a Git repository
flux bootstrap github \
--owner=my-org \
--repository=my-fleet \
--branch=main \
--path=clusters/production \
--personal # use personal access token
# Flux commits its own manifests to the repo and installs them
# The cluster is now managed by GitOps
|
Repository Structure#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| my-fleet/
├── clusters/
│ ├── production/
│ │ ├── flux-system/ # Flux's own manifests (auto-generated)
│ │ ├── sources.yaml # GitRepository, HelmRepository sources
│ │ └── apps.yaml # Kustomization pointing to apps/
│ └── staging/
├── infrastructure/
│ ├── cert-manager/
│ ├── nginx-ingress/
│ └── monitoring/
└── apps/
├── production/
│ ├── api/
│ └── worker/
└── staging/
|
Defining a Source#
1
2
3
4
5
6
7
8
9
10
11
12
13
| # clusters/production/sources.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-apps
namespace: flux-system
spec:
interval: 1m # poll interval
url: https://github.com/my-org/my-apps
ref:
branch: main
secretRef:
name: github-deploy-key
|
Deploying with Kustomize#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # clusters/production/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: my-apps
path: ./apps/production
prune: true # delete resources removed from Git
wait: true # wait for resources to become ready
timeout: 5m
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: api
namespace: production
|
1
2
3
4
5
6
7
8
9
10
| # apps/production/api/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
images:
- name: my-registry/api
newTag: "2.3.1" # image tag updated by image automation
|
Deploying Helm Charts with Flux#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # apps/production/nginx-ingress/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: nginx-ingress
namespace: flux-system
spec:
interval: 1h
chart:
spec:
chart: ingress-nginx
version: "4.9.0"
sourceRef:
kind: HelmRepository
name: ingress-nginx
namespace: flux-system
targetNamespace: ingress-nginx
install:
createNamespace: true
values:
controller:
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 128Mi
|
Image Automation#
Flux can automatically update image tags in Git when new images are pushed to a registry.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| # Image policy: track semver tags
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: api
namespace: flux-system
spec:
image: my-registry.example.com/api
interval: 1m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: api
namespace: flux-system
spec:
imageRepositoryRef:
name: api
policy:
semver:
range: ">=2.0.0 <3.0.0" # track 2.x.x releases
---
# Automation: commit image tag updates to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: my-apps
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxbot@example.com
name: Flux
messageTemplate: "Auto-update images to latest"
push:
branch: main
update:
path: ./apps
strategy: Setters
|
1
2
3
4
| # Marker in kustomization.yaml for image automation
images:
- name: my-registry/api
newTag: "2.3.1" # {"$imagepolicy": "flux-system:api:tag"}
|
Notifications#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # Send Slack alert on failed reconciliation
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Alert
metadata:
name: on-call-slack
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
---
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: "#alerts-production"
secretRef:
name: slack-webhook-url
|
Monitoring Flux#
1
2
3
4
5
6
7
8
9
10
11
12
| # View reconciliation status
flux get all -n flux-system
# View recent events
flux events --watch
# Force reconciliation (don't wait for interval)
flux reconcile source git my-apps
flux reconcile kustomization apps
# View diff before Flux applies
flux diff kustomization apps
|
Conclusion#
Flux turns cluster state management from an imperative process (running commands) to a declarative one (updating Git). Prune enables automatic cleanup of removed resources. Image automation closes the loop from CI (build and push image) to CD (update cluster). Start with Kustomize-based deployments, add HelmReleases for third-party charts, and enable image automation once the basic GitOps flow is stable.