Was ist GitOps?
GitOps setzt sich aus Git und Operations zusammen und beschreibt dass die Infrastruktur (infrastructure as code) selbst auch in einem Git Repository gehostet wird.
Es gibt also bei dem GitOps Verfahren zwei unterschiedliche Repositorys. Ein Repository für den Application Code und ein Repository für die Verwaltung der Infrastruktur in dem die Anwendung laufen soll. Da die Infrastruktur nun auch in einem Git Repository verwaltet wird, lässt sich zu jedem Zeitpunkt und/oder an anderen Orten eine identische Kopie der Anwendung schnell aufbauen. Auch die Nachverfolgbarkeit ist somit gegeben. Bei guter Dokumentation in den Commit Messages, kann jede Änderung auch gut nachvollzogen werden.
Push und Pull
Es gibt für GitOps zwei Varianten wie das realisiert werden kann. Es gibt Push und Pull-basierende Verfahren. Push-basierend bedeutet, dass von außen eine Anwendung die getätigten Änderungen in dem Git Repository im Kubernetes Cluster durchführt. Das klingt erst einmal gut, wenn man aus einer CI/CD Pipeline heraus die Änderungen durchführen kann. Das hat aber einen entscheidenden Nachteil, eine Anwendung von außen hat vollen Zugriff auf den Cluster. Bei dem Pull-basierenden Verfahren geschiet dieses aus dem Cluster heraus und ist daher für den Betrieb also sicherer. Ein Vertreter der Pull-basierenden ist Fluxcd und dieses werden ich hier in diesem Artikel mit einem lokalen Git Repository, welches mit Gitea gehostet wird verwenden, um die Infrastruktur der Anwendung zu beschreiben.
Fluxcd mit lokalen Gitea Host
Nun beschreibe ich wie man GitOps mit Fluxcd umsetzt. Als Werkzeuge hierfür verwende ich K3D, K3S und K9S.
Vorbereitungen
Cluster starten
Für das Demo habe ich einen kleinen Cluster mit
k3d create cluster Demo -w 3 -p 80:80@loadbalancer
erstellt. Damit kubectl und K9S auf den Cluster zugreifen können, müssen wir die Kubeconfig mit K3D mergen mit
k3d get kubeconfig -a
nun greifen die Tools auf den neu erstellten Cluster zu. Hier ein Screenshot von K9S mit dem man einen Kubernetes Cluster schön einfach über die Konsole inspizieren kann.
Alias für Kubectl
Wer es noch nicht eingerichtet hat, der sollte um die Schreibarbeit zu minimieren, ein alias k für kubectl mit
alias k=kubectl
einrichten.
Sollte kubectl noch nicht auf dem System installiert sein, dann kann es einfach mit:
yay -S kubectl
installiert werden.
Kubernetes Namespace
Es ist zu empfehlen, dass fluxcd in einem eigenen Kubernetes Namespace läuft. Ich wähle hierfür flux aus.
k create ns flux
Alternativ kann man den Namespace über ein Kubernetes Manifest flux.namespace.yaml anlegen lassen:
apiVersion: v1
kind: Namespace
metadata:
name: flux
Dann noch ein kubectl apply mit:
k apply -f flux-namespace.yaml
Erstellung known_hosts Datei
Es gibt 2 Varianten wie auf das Gitea Repository zugegriffen werden kann. Per HTTPS und SSH. Ich habe mich für die SSH Variante entschieden, weil so keine Credentials im Connection String angegeben werden muss und somit auch kein User vorhanden sein muss. Ich verwende SSH, weil so die Deployment Keys verwendet werden und ich der Anwendung fluxcd auf genau ein Repository Zugriff gewähren kann.
Damit per SSH auf das Gitea Repository zugegriffen werden kann, muss in dem fluxcd Kubernetes Deployment eine known_hosts Datei vorhanden sein, da sonst ein Zugriff auf das Gitea Repository nicht möglich ist und somit fluxcd das Repository nicht überwachen kann. Also muss man mit diese mit ssh-keyscan vorab erstellen.
ssh-keyscan gitea.xxx.org > known_hosts
Achtung: Falls der Git Server auf einem anderen Port lauscht, dann muss dieser mit -p spezifiziert werden. Nur so erhält man die richtigen Host Keys und eine korrekte known_hosts Datei.
Damit später aus dem Deployment von Fluxcd (siehe unten) darauf zugegriffen werden kann, muss die Datei als ConfigMap eingebunden werden.
k create configmap flux-ssh-config --from-file=known_hosts -n flux
Nun ist die ConfigMap in dem Namespace flux als flux-ssh-config bekannt. Darauf werden wir unten zugreifen.
YAML erstellen
Mit fluxctl install wird eine eine Multidatei YAML erzeugt. Diese wird über die Pipe per kubectl apply normalerweise direkt angewendet. Da wir aber für den Betrieb noch ein paar Anpassungen vornehmen müssen, leiten wir die Ausgabe in die Datei fluxcd.yaml um.
Zunächst muss fluxctl auf dem System installiert werden:
yay -S fluxctl
Danach erzeugen wir uns eine initiale YAML, die wir im Anschluss noch modifizieren werden.
fluxctl install --git-user=sascha --git-email=MrPeacock@web.de --git-url="gitea@mrpeacock.duckdns.org:Kubernetes/fluxdemo.git" --git-path=namespaces,workloads --namespace=flux > fluxcd.yaml
Anpassen der fluxcd.yaml
Known_hosts Datei per ConfigMap bereitstellen
# The following volume is for using a customised known_hosts
# file, which you will need to do if you host your own git
# repo rather than using github or the like. You'll also need to
# mount it into the container, below. See
# https://docs.fluxcd.io/en/latest/guides/use-private-git-host.html
- name: ssh-config
configMap:
name: flux-ssh-config
Die ConfigMap muss dem Cluster in dem vorher definierten Namespace flux bereitgestellt werden.
k create configmap flux-ssh-config --from-file=known_hosts -n flux
Jetzt muss die ConfigMap nur noch in den Container unter den Pfad /root/.ssh gemounted werden.
# Include this if you need to mount a customised known_hosts
# file; you'll also need the volume declared above.
- name: ssh-config
mountPath: /root/.ssh
Nur lese Modus
FluxCD bietet einen nur lese Modus an. Dieses ist praktisch, wenn man die volle Kontrolle über den Cluster behalten möchte. Anderenfalls scannt FluxCD die Imagerepository und aktualisiert per Commit neuere Versionen in den Kubernetes Manifesten.
# Tell flux it has readonly access to the repo (default `false`)
- --git-readonly
Einen Hostname einer festen IP Addresse zuordnen
Falls ein Dienst nicht per Namensauflösung im Container aufgerufen werden kann, dann kann man Hostnames definieren.
#
# map git.xxx.org to 192.168.2.100
#
hostAliases:
- ip: "192.168.2.1"
hostnames:
- "git.xxx.org"
Fertige Datei
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: flux
namespace: flux
spec:
replicas: 1
selector:
matchLabels:
name: flux
strategy:
type: Recreate
template:
metadata:
annotations:
prometheus.io/port: "3031" # tell prometheus to scrape /metrics endpoint's port.
labels:
name: flux
spec:
nodeSelector:
beta.kubernetes.io/os: linux
serviceAccountName: flux
volumes:
- name: git-key
secret:
secretName: flux-git-deploy
defaultMode: 0400 # when mounted read-only, we won't be able to chmod
# This is a tmpfs used for generating SSH keys. In K8s >= 1.10,
# mounted secrets are read-only, so we need a separate volume we
# can write to.
- name: git-keygen
emptyDir:
medium: Memory
# The following volume is for using a customised known_hosts
# file, which you will need to do if you host your own git
# repo rather than using github or the like. You'll also need to
# mount it into the container, below. See
# https://docs.fluxcd.io/en/latest/guides/use-private-git-host.html
- name: ssh-config
configMap:
name: flux-ssh-config
# The following volume is for using a customised .kube/config,
# which you will need to do if you wish to have a different
# default namespace. You will also need to provide the configmap
# with an entry for `config`, and uncomment the volumeMount and
# env entries below.
# - name: kubeconfig
# configMap:
# name: flux-kubeconfig
# The following volume is used to import GPG keys (for signing
# and verification purposes). You will also need to provide the
# secret with the keys, and uncomment the volumeMount and args
# below.
# - name: gpg-keys
# secret:
# secretName: flux-gpg-keys
# defaultMode: 0400
#
# map git.xxx.org to 192.168.2.1
#
hostAliases:
- ip: "192.168.2.1"
hostnames:
- "git.xxx.org"
containers:
- name: flux
# There are no ":latest" images for flux. Find the most recent
# release or image version at https://hub.docker.com/r/fluxcd/flux/tags
# and replace the tag here.
image: docker.io/fluxcd/flux:1.18.0
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 50m
memory: 64Mi
ports:
- containerPort: 3030 # informational
livenessProbe:
httpGet:
port: 3030
path: /api/flux/v6/identity.pub
initialDelaySeconds: 5
timeoutSeconds: 5
readinessProbe:
httpGet:
port: 3030
path: /api/flux/v6/identity.pub
initialDelaySeconds: 5
timeoutSeconds: 5
volumeMounts:
- name: git-key
mountPath: /etc/fluxd/ssh # to match location given in image's /etc/ssh/config
readOnly: true # this will be the case perforce in K8s >=1.10
- name: git-keygen
mountPath: /var/fluxd/keygen # to match location given in image's /etc/ssh/config
# Include this if you need to mount a customised known_hosts
# file; you'll also need the volume declared above.
- name: ssh-config
mountPath: /root/.ssh
# Include this and the volume "kubeconfig" above, and the
# environment entry "KUBECONFIG" below, to override the config
# used by kubectl.
# - name: kubeconfig
# mountPath: /etc/fluxd/kube
# Include this to point kubectl at a different config; you
# will need to do this if you have mounted an alternate config
# from a configmap, as in commented blocks above.
# env:
# - name: KUBECONFIG
# value: /etc/fluxd/kube/config
# Include this and the volume "gpg-keys" above, and the
# args below.
# - name: gpg-keys
# mountPath: /root/gpg-import
# readOnly: true
# Include this if you want to supply HTTP basic auth credentials for git
# via the `GIT_AUTHUSER` and `GIT_AUTHKEY` environment variables using a
# secret.
# envFrom:
# - secretRef:
# name: flux-git-auth
args:
# If you deployed memcached in a different namespace to flux,
# or with a different service name, you can supply these
# following two arguments to tell fluxd how to connect to it.
# - --memcached-hostname=memcached.default.svc.cluster.local
# Use the memcached ClusterIP service name by setting the
# memcached-service to string empty
- --memcached-service=
# This must be supplied, and be in the tmpfs (emptyDir)
# mounted above, for K8s >= 1.10
- --ssh-keygen-dir=/var/fluxd/keygen
# Replace the following URL to change the Git repository used by Flux.
# HTTP basic auth credentials can be supplied using environment variables:
# https://$(GIT_AUTHUSER):$(GIT_AUTHKEY)@github.com/user/repository.git
- --git-url=gitea@git.xxx.org:Kubernetes/fluxdemo.git
- --git-branch=master
- --git-path=namespaces,workloads
- --git-label=flux
- --git-user=sascha
- --git-email=sascha@edvpfau.de
# Include these two to enable git commit signing
# - --git-gpg-key-import=/root/gpg-import
# - --git-signing-key=<key id>
# Include this to enable git signature verification
# - --git-verify-signatures
# Tell flux it has readonly access to the repo (default `false`)
- --git-readonly
# Instruct flux where to put sync bookkeeping (default "git", meaning use a tag in the upstream git repo)
# - --sync-state=git
# Include these next two to connect to an "upstream" service
# (e.g., Weave Cloud). The token is particular to the service.
# - --connect=wss://cloud.weave.works/api/flux
# - --token=abc123abc123abc123abc123
# Enable manifest generation (default `false`)
# - --manifest-generation=false
# Serve /metrics endpoint at different port;
# make sure to set prometheus' annotation to scrape the port value.
- --listen-metrics=:3031
# Optional DNS settings, configuring the ndots option may resolve
# nslookup issues on some Kubernetes setups.
# dnsPolicy: "None"
# dnsConfig:
# options:
# - name: ndots
# value: "1"
---
apiVersion: v1
kind: Secret
metadata:
name: flux-git-deploy
namespace: flux
type: Opaque
---
# memcached deployment used by Flux to cache
# container image metadata.
apiVersion: apps/v1
kind: Deployment
metadata:
name: memcached
namespace: flux
spec:
replicas: 1
selector:
matchLabels:
name: memcached
template:
metadata:
labels:
name: memcached
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: memcached
image: memcached:1.5.20
imagePullPolicy: IfNotPresent
args:
- -m 512 # Maximum memory to use, in megabytes
- -I 5m # Maximum size for one item
- -p 11211 # Default port
# - -vv # Uncomment to get logs of each request and response.
ports:
- name: clients
containerPort: 11211
securityContext:
runAsUser: 11211
runAsGroup: 11211
allowPrivilegeEscalation: false
---
apiVersion: v1
kind: Service
metadata:
name: memcached
namespace: flux
spec:
ports:
- name: memcached
port: 11211
selector:
name: memcached
---
# The service account, cluster roles, and cluster role binding are
# only needed for Kubernetes with role-based access control (RBAC).
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
name: flux
name: flux
namespace: flux
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
labels:
name: flux
name: flux
rules:
- apiGroups: ['*']
resources: ['*']
verbs: ['*']
- nonResourceURLs: ['*']
verbs: ['*']
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
labels:
name: flux
name: flux
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flux
subjects:
- kind: ServiceAccount
name: flux
namespace: flux
Starten
k apply -f ./fluxcd.yaml
Warten bis das Rollout durch ist
k -n flux rollout status deployment/flux
Die Deploy Keys auslesen
Beim Start erstellt fluxcd automatisch eine Identität, mit der dann auf das Git Repository zugegriffen werden kann. Dieses kann man mit in der Logs greppen…
k logs deployment/flux -n flux | grep pub
aber fluxcd kennt mit fluxctl identity eine eigene Anweisung, um die Keys zu extrahieren.
fluxctl identity --k8s-fwd-ns flux
Achtung: Die Deploy Keys werden nur erzeugt, wenn man das SSH Protokoll verwendet! FluxCD kann auch eine Verbindung zu dem Git Repository über eine HTTPS Verbindung aufbauen (siehe oben).
Sync manuell anstoßen
Normalerweise prüft fluxcd alle 5 Minuten das repository auf Änderungen. Gerade für den 1. Test ist das zu lange und daher stoßen wir mit fluxctl sync den Vorgang manuell an.
fluxctl sync --k8s-fwd-ns=flux