Getting Started with GitOps on Your Local Machine: A Beginner’s Guide Using Flux

Mehmet Ali Baykara
5 min readApr 24, 2023

--

Managing workloads in Kubernetes can be efficiently achieved using the GitOps approach, whether you are in the production or development environment.

https://cncf-branding.netlify.app/projects/flux/

GitOps is a methodology that centralizes infrastructure and application deployment management for Kubernetes using Git. It automates the deployment process and ensures consistency with the desired state in the Git repository.

Flux is a popular open-source tool that enables GitOps in Kubernetes by automating the deployment and synchronization of container images and Kubernetes manifests from a Git repository to a cluster.

GitLab, GitHub, and Bitbucket are commonly used as Git providers for version control systems. However, Flux works with any Git provider, including bare Git servers, making it a versatile tool for GitOps in Kubernetes. As this demo will be run locally, I plan to install Git inside an Alpine-based Docker container and use it as a Git server within the cluster.

Tools:

  • A Kubernetes Cluster i.e. Kind, k3s, k3d or Minikube

With k3s as my chosen tool, I’ve created a detailed script that effortlessly sets up a fully working k3s cluster, including the deployment of Flux and its synchronization with the Git repository, ensuring the smooth application of desired states to your apps. Without further ado, let’s go into the details of the script and explore each step with the utmost respect.

From lines 1–10

#!/bin/bash
set -euo pipefail

#Install docker
apt update && apt install -y docker.io
#Install k3s
curl -sfL https://get.k3s.io | sh -

# Install Flux CLI in order bootstrap Flux
curl -s https://fluxcd.io/install.sh | sudo bash

From lines 11–42

After the commands above, the k3s cluster should be up and running.

# Creating `registry` namespace
kubectl create ns registry
# Creating deployment that is my Image registry. Hence I don't want use dockerhub
kubectl apply -f container-registry.yaml

#before push the artifacts there, wait until get it in ready
kubectl wait --timeout=90s --for=condition=available deployment private-registry -n registry

registry_name=$(kubectl -n registry get pod -o=jsonpath='{.items[0].metadata.name}')
# Get the IP address of the pod
registry_ip=$(kubectl -n registry get pod "$registry_name" -o=jsonpath='{.status.podIP}')
# Add the IP of the pod to your local hosts in order to push/pull image from your local machine.
echo "$registry_ip registry" >>/etc/hosts

#Tell docker daemon to allow insecure registry
cat << EOF > /etc/docker/daemon.json
{
"insecure-registries": ["registry:5000"]
}
EOF
#restart docker engine to reload the new config we just created
systemctl restart docker



# Generate a timestamp for tagging the image and the directory
timestamp=$(date +%d%m%y)
# Create a temporary directory for generating SSH keys
key_dir=$(mktemp -d --suffix="$timestamp")

# Generate SSH keys in order to communicate with git server via ssh
ssh-keygen -t rsa -N "" -f "$key_dir/id_rsa"
#copy ssh key pair to keys dir
mkdir -p /root/.kube ./keys
cp $key_dir/* ./keys

With the k3s cluster and image registry now fully operational under the registry namespace, we can confidently proceed with the deployment of our Git instance as a deployment. To achieve this, it is imperative that we first create a fresh docker image, complete with pre-installed Git and an SSH key that will enable us to clone and push with ease. Here is our Dockerfile

#This file is slightly modified version of https://github.com/fluxcd/gitsrv/blob/master/Dockerfile
FROM alpine:3.15

RUN apk add --no-cache openssh git curl bash gnupg

RUN ssh-keygen -A

WORKDIR /git-server/

RUN mkdir /git-server/keys \
&& adduser -D -s /usr/bin/git-shell git \
&& mkdir /home/git/.ssh \
&& mkdir /home/git/git-shell-commands \
&& apk --update add tar zip unzip

ARG password
RUN sh -c "echo git:${password:-ratherchangeme} |chpasswd"

#basic ssh config
COPY config/sshd_config /etc/ssh/sshd_config
#init.sh copy add ssh key to authorized key which lets anyone
#who uses the private key to clone/push/pull
COPY config/init.sh init.sh

EXPOSE 22

CMD ["sh", "init.sh"]

The Dockerfile simply installs various tools, creates the Git user, and copies a recently generated public SSH key into the image. This allows us to bootstrap Flux with the private key, enabling Flux to communicate with the Git server. Let's build and publish the image to our registry that runs in the Kubernetes cluster.

From lines 43–46


docker build -t "registry:5000/gitserver:$timestamp" .
# Push the Docker image to your local registry runs in the Kubernetes
docker push registry:5000/gitserver:$timestamp
wait

Now we are ready to deploy our Git server from the image above. Since we are using k3s, we need to tell k3s should pull images from registry:5000 rather than the docker hub.

From lines 53–64

cp /etc/rancher/k3s/k3s.yaml /root/.kube/config

#tell k3s pull images from local registry rather than docker hub
cat << EOF > /etc/rancher/k3s/registries.yaml
mirrors:
"registry:5000":
endpoint:
- "http://registry:5000"
EOF
systemctl restart k3s

We can create flux-system namespace and secret that ssh keys contain.

From lines ca 53–64

kubectl create ns flux-system
# Create a Kubernetes secret with the generated SSH key
kubectl -n flux-system create secret generic flux-git-key \
--from-file="$key_dir/id_rsa" \
--from-file="$key_dir/id_rsa.pub"

# Build a Docker image with the generated keys and tag it with the timestam
# Update the deployment file with the new image tag
sed -i "s/registry:5000\/gitserver:.*/registry:5000\/gitserver:$timestamp/" deployment.yaml
kubectl apply -f deployment-gitserver.yaml
#keep wait until git server is ready
kubectl wait --timeout=90s --for=condition=available deployment gitserver -n flux-system

So far, everything is set up. We can deploy Flux:

As I utilize a Git server that runs within my cluster, I must access it through the Pod IP, since exposing an ingress via a public IP is unnecessary. In order to accomplish this, I must add the Pod IP to my local hosts file.

# Get the name of the first pod in the cluster
pod_name=$(kubectl -n flux-system get pod -o=jsonpath='{.items[0].metadata.name}')
# Get the IP address of the pod
pod_ip=$(kubectl -n flux-system get pod "$pod_name" -o=jsonpath='{.status.podIP}')

#only required for communicating with pod from local machine
echo "$pod_ip gitserver" >>/etc/hosts

Now we can bootstrap Flux


flux bootstrap git \
--url="ssh://git@gitserver/git-server/repos/cluster.git" \
--branch=main \
--path=clusters \
--private-key-file=./keys/id_rsa

Flux will push all its resources into cluster.git repo

At the End:

The Flux controllers are currently active, and in addition to them, there is also a Nginx deployment running. The deployment used in this scenario can be found at the following source. Once the deployment is up and running, you can clone the repository from your Git server (which is running on the cluster under the Pod name gitserver-658ffd57f7-gkrpv).

Simply:

#First populate the ssh key
eval "$(ssh-agent -s)"
ssh-add keys/id_rsa
git clone ssh://git@gitserver/git-server/repos/cluster.git

By committing to the repository, you are able to deploy new applications, and Flux will then automatically reconcile and apply the configuration changes to the cluster.

The goal of this blog is to enable readers to run GitOps workloads locally without relying on Docker or Git hosting platforms such as GitHub or GitLab. If there are any uncertainties or questions, please do not hesitate to ask.

Update:
Here is the source code for script and co here

--

--

Mehmet Ali Baykara
Mehmet Ali Baykara

Written by Mehmet Ali Baykara

Don't buy me a coffee! Just Say "Hi"

Responses (1)