Hands-On with FluxCD: A Practical Guide to GitOps for Beginners

KubeSkillsKubeSkills
10 min read

Whether you're a developer, DevOps engineer, sysadmin, or platform engineer, you've heard of GitOps. GitOps is a methodology that extends the CI/CD model, allowing teams to manage infrastructure and application deployments using Git as a single source of truth. Using GitOps, however, might not be as straightforward as it seems. Some would even argue that its hidden complexity is not worth the hassle. GitOps with FluxCD can help navigate these complexities, as FluxCD simplifies continuous delivery through its built-in reconciliation engine and more.

In this beginner's guide, I aim to break down some of the complexity and deliver some tacit knowledge, allowing you to get hands-on with FluxCD. I'll show you the benefits of FluxCD, so you feel confident getting up and running. Also, you'll learn by doing an actual project!

What is FluxCD?

FluxCD is an open-source continuous delivery tool designed for Kubernetes. It works by continuously monitoring your Git repository and applying any changes to your Kubernetes cluster. With FluxCD, you can automate updates and rollbacks and even monitor your deployments.

Key Features of FluxCD

  • GitOps: FluxCD leverages Git as the single source of truth, enabling declarative management of infrastructure and application deployments.

  • Automated Deployments: FluxCD continuously monitors your Git repository and automatically updates your Kubernetes cluster with the desired state.

  • Syncing and Reconciliation: FluxCD ensures that the live state of your cluster matches the state defined in your Git repository.

  • Multi-Tenancy: FluxCD supports managing multiple environments, allowing you to use the same Git repository for staging, production, and more.

  • Integrations: FluxCD integrates well with tools like Helm, Prometheus, and others to provide a comprehensive solution for GitOps.

Core Components of FluxCD

Before we dive into the hands-on part, let's briefly discuss the core components of FluxCD:

  1. Flux is the primary operator (control plane), continuously monitoring the Git repository and applying changes to the Kubernetes cluster.

  2. The Helm Controller is a component of FluxCD that allows you to manage Helm charts in a GitOps fashion.

  3. The Kustomize Controller supports managing Kubernetes manifests using Kustomize, enabling you to customize your deployments.

  4. The Notification Controller handles notifications and alerts for your deployments.

  5. The Source Controller monitors the source of the Kubernetes manifests (Git, Helm repositories, etc.) and makes them available for the other controllers.

Creating a GitHub Personal Access Token (PAT)

Since FluxCD uses Git as the single source of truth for deployments, the apparent prerequisite is a Git hosting service. Flux CLI creates a new GitHub repository for you, but it needs a personal access token (PAT) to do so.

Create a new PAT by going to the following GitHub link: https://github.com/settings/tokens/new

Select the checkmark box next to "repo," as shown below, then click "Generate Token." Copy the PAT, as you will use it for the next step!

Installing FluxCD

FluxCD can be installed using the flux CLI tool. Here's how you can set it up on your Kubernetes cluster. For free access to a cluster, head to Killercoda.com and follow along!

  1. Install the Flux CLI:

     curl -s https://fluxcd.io/install.sh | sudo bash
    
  2. Bootstrap FluxCD:

    The bootstrap command installs the FluxCD components and configures them to reconcile your cluster state with a Git repository (replace <github-username> with your GitHub username).

     flux bootstrap github \
       --owner=<github-username> \
       --repository=my-flux-repo \
       --branch=main \
       --path=./clusters/my-cluster \
       --personal=true \
       --private=false
    
  3. Verify the Installation:

    After bootstrapping, you can verify that FluxCD is running correctly using the command flux check --pre which will check the health of the Flux components in your Kubernetes cluster.

     flux check --pre
    
  4. Clone the Repo:
    After running the flux bootstrap github command, you should clone the GitHub repository that Flux created during the bootstrap process. This allows you to add your application files (e.g., Kubernetes manifests, Helm charts, or Kustomize files) to the repository. These files define the desired state of your applications, which Flux will then monitor and apply to your Kubernetes cluster (replace <github-username> with your GitHub username).

     git clone https://github.com/<github-username>/my-flux-repo.git
    
  5. Create your Application Directory
    Inside the cloned repository, you will see the directory structure created by Flux. Typically, a specific directory is created to store the application manifests, helm charts or Kustomize configurations (e.g. apps/my-app). Create the directory where your application files will go.

     mkdir -p my-flux-repo/apps/my-app
    
  6. (Optional) Organize Your Repository
    For larger projects, you can structure your repository into separate directories for staging, production, and other environments. The following is an example of this organizational pattern, which allows you to define shared configurations in the base directory and customize them for each specific environment in the overlays directory, without duplicating code.

     apps/
     ├── my-app/
     │   ├── base/
     │   │   ├── deployment.yaml
     │   │   └── service.yaml
     │   ├── overlays/
     │       ├── staging/
     │       └── production/
     clusters/
     ├── staging/
     │   └── kustomization.yaml
     └── production/
         └── kustomization.yaml
    

Building a Sample Microservices Application

In this example, we'll create a simple microservices-based application consisting of two services:

  1. Frontend Service: A simple web server built using Node.js that serves static content.

  2. Backend Service: A Python-based API service that provides some data to the frontend.

For most Kubernetes-based setups using FluxCD, the best practice is to store application code and deployment manifests in separate repositories. This adheres to GitOps principles, where your Git repository serves as the single source of truth for your cluster's desired state. Therefore, we’ll create a new application repository that contains our application code.

mkdir src && cd src

Frontend Service

The frontend service will be a basic Node.js application that serves an HTML page. Here's the code:

cat << EOF > index.js
// index.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('<h1>Welcome to the Frontend Service</h1>');
});

app.listen(port, () => {
  console.log('Frontend service is running on http://localhost:${port}');
});
EOF

Create a Dockerfile for the frontend service:

cat << EOF > JS-Dockerfile
# JS-Dockerfile
FROM node:14-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000
CMD ["node", "index.js"]
EOF

Backend Service

The backend service will be a simple Python Flask application that returns some JSON data. Here's the code:

cat << EOF > app.py
# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/data', methods=['GET'])
def get_data():
    data = {
        'message': 'Hello from the Backend Service!'
    }
    return jsonify(data)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF

Create a Dockerfile for the backend service:

cat << EOF > Py-Dockerfile
# Dockerfile
FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000
CMD ["python", "app.py"]
EOF

Create the requirements.txt file:

echo "flask" > requirements.txt

create a package.json file:

# package.json
cat << EOF > package.json
{
  "name": "flux-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "express": "^4.17.1"
  },
  "scripts": {
    "test": ""
  },
  "author": "",
  "license": "ISC"
}
EOF

Build the Container Images

We'll build the container images for both the front and backend apps using the Docker CLI. Use the following commands to build and tag the images using the two separate Dockerfile files named JS-Dockerfile and Py-Dockerfile:

docker build -f JS-Dockerfile -t localhost:5000/my-frontend .
docker build -f Py-Dockerfile -t localhost:5000/my-backend .

Create a container registry locally:

docker run --name local-registry -d -p 5000:5000 registry

Push the container images to the local registry:

docker push localhost:5000/my-backend
docker push localhost:5000/my-frontend

Creating Kubernetes Manifests

Next, we'll create Kubernetes manifests for deploying these two services, placing them in the repo that we created and cloned at the beginning, in my-flux-repo/apps/my-app. Use your favorite text editor to create these files in the my-app directory.

cd $HOME/my-flux-repo/apps/my-app

Frontend Deployment Manifest

# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
  labels:
    app: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: localhost:5000/my-frontend:latest
        ports:
        - containerPort: 3000

Frontend Service Manifest

# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
  namespace: default
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

Backend Deployment Manifest

# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: default
  labels:
    app: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: localhost:5000/my-backend:latest
        ports:
        - containerPort: 5000

Backend Service Manifest

# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000

Setting Up FluxCD to Deploy the Application

Now that we have our application and Kubernetes manifests ready, let's set up FluxCD to deploy them.

Step 1: Create a Kustomization File

Also in the my-app directory, create a kustomization.yaml file. This file tells FluxCD to apply the resources defined in the specified YAML files.

The kustomization.yaml file serves as a configuration file for Kustomize, a Kubernetes-native configuration management tool. It defines how Kubernetes manifests in that directory should be aggregated, customized, or patched before being applied to your cluster.

In a GitOps setup, such as with FluxCD, this file helps manage and deploy the application's resources by describing how the various Kubernetes YAML files in the my-app directory (like frontend-deployment.yaml, frontend-service.yaml, etc.) should be combined and customized.

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - frontend-deployment.yaml
  - frontend-service.yaml
  - backend-deployment.yaml
  - backend-service.yaml
  1. Link the Application Directory to FluxCD
    In the clusters/my-cluster/flux-system/kustomization.yaml file (already created from the bootstrap process), add a reference to your application's kustomization.yaml file. This tells FluxCD to apply the resources defined in apps/my-app. Edit the clusters/my-cluster/flux-system/kustomization.yaml file and add the path to your application directory.
cd $HOME/my-flux-repo/clusters/my-cluster/flux-system
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - gotk-components.yaml
  - gotk-sync.yaml
  - ../../../apps/my-app/ # add this line

What Happens When FluxCD Reconciles It?

  1. FluxCD Detects Changes:

    • If you make a change in the Git repository (e.g., update kustomization.yaml or a resource file), FluxCD detects the change.
  2. FluxCD Applies the Kustomization:

    • FluxCD reads the kustomization.yaml file in the apps/my-app directory.

    • It processes the listed resources, applies the customizations (e.g., patches, labels), and reconciles the desired state with the cluster.

  3. Resources Are Deployed:

    • The aggregated and customized manifests are applied to the cluster, ensuring the application is deployed in the desired configuration.

Commit and Push Changes

After updating the kustomization.yaml, commit and push the changes to your Git repository.

cd $HOME/my-flux-repo

git config --global user.email "you@example.com"

git config --global user.name "Your Name"

git add .; git commit -m "add my-app to cluster configuration"; git push origin main

NOTE: When prompted for your GitHub password, paste in your GitHub PAT (the one we generated at the beginning).

Verify FluxCD Reconciliation

Wait for FluxCD to reconcile the changes or manually trigger it. Check the logs for updates:

  • Trigger Reconciliation:

      flux reconcile kustomization flux-system -n flux-system
    
  • Check Logs:

      kubectl logs -n flux-system deploy/kustomize-controller
    
  • Check Kustomization Status:

      flux get kustomizations -n flux-system
    
  • Check that the app was created:

      kubectl get all
    

Summary

In this guide, we walked through the end-to-end process of setting up a GitOps workflow using FluxCD, from bootstrapping a Flux-enabled cluster to deploying a sample microservices application. Along the way, you learned how FluxCD simplifies the continuous delivery process through reconciliation, Git-based change tracking, and modular infrastructure design.

Key Takeaways

  • Git as the Source of Truth: With FluxCD, your Git repository drives all changes in your Kubernetes cluster, ensuring consistency and auditability.

  • Modular Design: You can organize your manifests using Kustomize, separating base configurations from overlays and manage multiple environments efficiently.

  • Declarative Deployment: All resources, from Deployments to Services, are declaratively described, version-controlled, and automatically applied via FluxCD.

  • End-to-End Automation: FluxCD monitors changes in Git and continuously reconciles your desired state with the actual state of your cluster, eliminating manual drift correction.

  • Hands-On Value: By deploying a working microservices application using Node.js and Flask, you’ve applied GitOps principles in a real-world scenario.

While GitOps can initially seem overwhelming, especially with the many moving parts of Kubernetes, CI/CD pipelines, and declarative tooling, FluxCD makes it approachable, scalable, and production-ready. The hands-on walkthrough you completed today is just the beginning.

Whether you’re just getting started or expanding GitOps across multiple environments, FluxCD provides a robust and secure foundation to manage your workloads the GitOps way.

If this guide helped demystify FluxCD, share it with your team or community! And if you’re ready for more advanced topics, like multi-tenancy, pull-based secrets management, and progressive delivery, stay tuned for our next post!

0
Subscribe to my newsletter

Read articles from KubeSkills directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

KubeSkills
KubeSkills