Post

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.


Part 2: Deploying Traefik with FluxCD

This post is licensed under CC BY 4.0 by the author.