Utilize GitOps with FluxCD in Kubernetes Part 1
Manging Kubernetes with GitOps using Flux CD Part 1
In the ever-evolving landscape of home lab automation, managing Kubernetes clusters can quickly become a time-consuming endeavor. Traditional approaches often involve manual updates, complex deployment scripts, or custom automation solutions that require constant maintenance. However, by embracing GitOps principles and leveraging FluxCD, you can transform your homelab Kubernetes environment into a self-healing, declarative system that automatically syncs with your desired state. This approach not only saves you time on routine maintenance but also gives you peace of mind knowing your services are always running the latest versions. In this guide, we’ll explore how to implement FluxCD in your homelab to manage services, handle updates, and maintain consistency across your Kubernetes cluster—all while keeping your infrastructure as code (IaC) in a single source of truth.
Install flux and initializing flux repo
In order to start down this path, you will need a kubernetes cluster available in your homelab. I recommend Techno Tim’s Fully Automated K3s install with Ansible.
Once complete, you should be able to run kubectl
commands from your local machien to your kubernetes cluster
1
2
3
4
5
6
7
8
kubectl get nodes --all-namespaces
NAME STATUS ROLES AGE VERSION
k3s-server01 Ready control-plane,etcd,master 110d v1.30.2+k3s2
k3s-server02 Ready control-plane,etcd,master 110d v1.30.2+k3s2
k3s-server03 Ready control-plane,etcd,master 110d v1.30.2+k3s2
k3s-worker01 Ready <none> 110d v1.30.2+k3s2
k3s-worker02 Ready <none> 110d v1.30.2+k3s2
k3s-worker03 Ready <none> 110d v1.30.2+k3s2
Start with installing fluxcd cli. I also recommend openlens which is a good local IDE for providing a view into your kubernetes cluster. You will also need a API token into your git system. This example will use my self-hosted gitlab server
1
brew install fluxcd/tap/flux openlens
Once installed, we can bootstrap our cluster for using fluxcd from our local machine that has kubectl access. Enter your git hostname, PAT (Personal Access Token) and let flux create and configure your fluxcd repo and configure your kubernetes cluster to accept fluxcd
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
flux bootstrap gitlab \
--components source-controller,kustomize-controller,helm-controller,notification-controller \
--components-extra image-reflector-controller,image-automation-controller \
--hostname=gitlab.schenk.tech \
--owner=homelab \
--repository=fluxcd \
--branch=main \
--path=clusters/home \
--token-auth --interval 1m
Please enter your GitLab personal access token (PAT):
► connecting to https://gitlab.schenk.tech
► cloning branch "main" from Git repository "https://gitlab.schenk.tech/homelab/fluxcd.git"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ component manifests are up to date
► installing components in "flux-system" namespace
✔ installed components
✔ reconciled components
► determining if source secret "flux-system/flux-system" exists
► generating source secret
► applying source secret "flux-system/flux-system"
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ sync manifests are up to date
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for GitRepository "flux-system/flux-system" to be reconciled
✔ GitRepository reconciled successfully
◎ waiting for Kustomization "flux-system/flux-system" to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ image-automation-controller: deployment ready
✔ image-reflector-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy
The following command will show the curernt status of the flux config
1
2
3
flux get all -A
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system gitrepository/flux-system main@sha1:d373c323 False True stored artifact for revision 'main@sha1:d373c323'
We can also check that fluxcd is in a healthy state
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
flux check
► checking prerequisites
✔ Kubernetes 1.30.2+k3s2 >=1.28.0-0
► checking version in cluster
✔ distribution: flux-v2.4.0
✔ bootstrapped: true
► checking controllers
✔ helm-controller: deployment ready
► ghcr.io/fluxcd/helm-controller:v1.1.0
✔ image-automation-controller: deployment ready
► ghcr.io/fluxcd/image-automation-controller:v0.39.0
✔ image-reflector-controller: deployment ready
► ghcr.io/fluxcd/image-reflector-controller:v0.33.0
✔ kustomize-controller: deployment ready
► ghcr.io/fluxcd/kustomize-controller:v1.4.0
✔ notification-controller: deployment ready
► ghcr.io/fluxcd/notification-controller:v1.4.0
✔ source-controller: deployment ready
► ghcr.io/fluxcd/source-controller:v1.4.1
► checking crds
✔ alerts.notification.toolkit.fluxcd.io/v1beta3
✔ buckets.source.toolkit.fluxcd.io/v1
✔ gitrepositories.source.toolkit.fluxcd.io/v1
✔ helmcharts.source.toolkit.fluxcd.io/v1
✔ helmreleases.helm.toolkit.fluxcd.io/v2
✔ helmrepositories.source.toolkit.fluxcd.io/v1
✔ imagepolicies.image.toolkit.fluxcd.io/v1beta2
✔ imagerepositories.image.toolkit.fluxcd.io/v1beta2
✔ imageupdateautomations.image.toolkit.fluxcd.io/v1beta2
✔ kustomizations.kustomize.toolkit.fluxcd.io/v1
✔ ocirepositories.source.toolkit.fluxcd.io/v1beta2
✔ providers.notification.toolkit.fluxcd.io/v1beta3
✔ receivers.notification.toolkit.fluxcd.io/v1
✔ all checks passed
Configure and deploy cert-manager and traefik
Now that our GitOps foundation is in place, let’s set up the essential services that will form the backbone of our cluster. First, we’ll deploy https://cert-manager.io/ to handle SSL/TLS certificates through Let’s Encrypt, ensuring secure HTTPS connections for all our services. We’ll configure it to use Cloudflare DNS for domain validation, which is perfect for homelab setups as it doesn’t require exposing your services to the public internet during certificate issuance. Alongside this, we’ll configure https://traefik.io/ as our ingress controller, which will manage external access to our applications and handle routing rules. These two components work together to provide a secure and flexible way to expose our services to the outside world, or in our case our homelab networks.
Configure cert-manager
We’ll start by creating three essential files for cert-manager: a namespace for resource isolation, a Helm repository configuration to access the cert-manager charts, and a secrets template for Cloudflare API credentials. The namespace provides a clean environment for our certificate management, while the Helm repository ensures we can install cert-manager from the official Jetstack charts. The secrets template, which we’ll populate later with your Cloudflare API key, will enable DNS validation for our certificates.
1
2
fluxcd/clusters/home on main
mkdir cert-manager
cert-manager-secrets.yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Secret
metadata:
name: cert-manager-secrets
namespace: cert-manager
annotations:
flux.weave.works/ignore: "true"
type: Opaque
# Update password with:
# kubectl create secret generic cert-manager-secrets --namespace cert-manager \
# --from-literal=CLOUDFLARE_API_KEY=<CLOUDFLARE_API_KEY> \
# --dry-run=client -o yaml | kubectl apply -f -
Add the helm-repo to install cert-manager via helm
helm-repo.yaml
1
2
3
4
5
6
7
8
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: cert-manager
namespace: flux-system
spec:
interval: 1m0s
url: https://charts.jetstack.io
Add the cert-manager namespace
namespace.yaml
1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
Add and commit the files to your git repository, then push them to trigger Flux to reconcile. Here are the git commands to do this:
1
2
3
4
5
6
7
8
# Add all the new files
git add .
# Create a commit with a descriptive message
git commit -m "Add cert-manager configuration with Cloudflare DNS validation"
# Push the changes to your repository
git push origin main
If you don’t want to wait for flux to reconcile, force it with the command below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜ flux reconcile source git flux-system
► annotating GitRepository flux-system in flux-system namespace
✔ GitRepository annotated
◎ waiting for GitRepository reconciliation
✔ fetched revision main@sha1:d373c323fe2e9876e064cb4d0e736c58c29a541b
~
➜ flux get all -A
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system gitrepository/flux-system main@sha1:d373c323 False True stored artifact for revision 'main@sha1:d373c323'
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system helmchart/cert-manager-cert-manager v1.17.2 False True pulled 'cert-manager' chart with version 'v1.17.2'
NAMESPACE NAME REVISION SUSPENDED READY MESSAGE
flux-system kustomization/flux-system main@sha1:d373c323 False True Applied revision: main@sha1:d373c323
After flux has successfully reconciled, update the cert-manager secret with the proper CLOUDFLARE_API_KEY
1
2
3
kubectl create secret generic cert-manager-secrets -n cert-manager \
--from-literal=CLOUDFLARE_API_KEY=<CLOUDFLARE_API_KEY> \
--dry-run=client -o yaml | kubectl apply -f -
Once complete, we can add the rest of the cert-manager configuration and install it in the new namespace. This includes setting up the HelmRelease that will actually install cert-manager from the Helm chart we defined earlier, along with configuring DNS settings for Let’s Encrypt validation. We’ll also prepare the Certificate and ClusterIssuer resources that will enable automatic TLS certificate management for our services.
helm-release.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: cert-manager
namespace: cert-manager
spec:
interval: 1m0s
chart:
spec:
chart: cert-manager
version: "*"
sourceRef:
kind: HelmRepository
name: cert-manager
namespace: flux-system
values:
installCRDs: true
dns01RecursiveNameserversOnly: true
dns01RecursiveNameservers: "8.8.8.8:53,1.1.1.1:53"
Now we’ll add the certificate and cluster-issuer configuration to prepare for Traefik’s deployment. This step involves creating two key resources: a ClusterIssuer that defines how cert-manager should obtain certificates from Let’s Encrypt using Cloudflare DNS validation, and a Certificate resource that will automatically request and manage the SSL/TLS certificate for our Traefik ingress. This setup ensures that when we deploy Traefik in part 2, it will immediately have a valid certificate ready to use, enabling secure HTTPS connections from the start.
certificate.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-schenk-tech
namespace: traefik
spec:
secretName: wildcard-schenk-tech
issuerRef:
kind: ClusterIssuer
name: letsencrypt-production
dnsNames:
- "*.schenk.tech"
cluster-issuer.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
namespace: cert-manager
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: email@domain.com
privateKeySecretRef:
name: letsencrypt-production
solvers:
- dns01:
cloudflare:
email: email@domain.com
apiKeySecretRef:
name: cert-manager-secrets
key: CLOUDFLARE_API_KEY
Finally, we’ll create a kustomization file to tie everything together. This file acts as the orchestrator for our cert-manager deployment, bringing together all the components we’ve configured: the namespace, the Helm release, and the cluster issuer. The kustomization file is a crucial piece of our GitOps setup as it defines the complete desired state of our cert-manager installation. When FluxCD processes this file, it will ensure all the pieces are deployed in the correct order and maintain the configuration we’ve specified. This approach not only keeps our configuration organized but also makes it easy to track changes through Git and roll back if needed.
kustomization.yaml
1
2
3
4
5
6
7
8
9
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- certificate.yaml
- cert-manager-secrets.yaml
- cluster-issuer.yaml
- helm-repo.yaml
- helm-release.yaml
- namespace.yaml
With cert-manager now configured and ready to handle our SSL/TLS certificates through Cloudflare DNS validation, we’ve laid the groundwork for secure service access in our homelab. In part 2, we’ll build upon this foundation by deploying Traefik as our ingress controller. We’ll not only set up Traefik itself but also implement its dashboard as our first test service, giving us a visual interface to monitor and manage our ingress routes. This will serve as a perfect example of how our GitOps workflow handles service deployments, and it will provide us with a useful tool for managing the rest of our services. Stay tuned for the next part where we’ll bring everything together and start exposing our first service to the network.