Preview Apps mit GitOps durch Flux und Kubernetes
Im vorherigen Artikel habe ich beschrieben, wie Flux die Ordner im Git Repository überwacht und automatisch den Zustand auf dem Cluster anpasst. Auf dieser Basis ist das Deployen einzelner Branches als Preview relativ einfach möglich.
- GitOps mit Kubernetes, Kustomize und Flux
- Preview Apps mit GitOps durch Flux und Kubernetes
Kustomize link
Durch Hinzufügen einer Kustomize-Datei zu einem Ordner können einzelne Werte im aktuellen Ordner und allen Unterordnern überschrieben werden.
Zu Beginn wird ein Ordner benötigt, in dem sich alle Previews befinden. Im folgenden Beispiel /previews
. In diesen Ordner lege ich eine Yaml-Datei (kustomization.yaml
), in der später alle Previews registriert werden. Zusätzlich können für alle Previews in Unterordnern Werte gesetzt werden.
Die folgende Datei befindet sich unter /previews/customization.yaml
. Weiterer Inhalt wird in der Pipeline generiert.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
Template link
Ich arbeite mit einem Template-Verzeichnis, da Kustomize die Werte nur bedingt überschreiben kann und ich eine einfache Konfiguration haben möchte. Ich lege folgende Datei unter /previews/template/kustomization.yaml
an.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prv-app-template
resources:
- namespace.yaml
- service-url.yaml
- config.yaml
- deployment.yaml
images:
- name: my-frontend-app
newName: ghcr.io/owner/repository
newTag: latest
Der namespace
wird in der Pipeline überschrieben.
Die resources
-Liste muss alle Kubernetes-Dateien enthalten, die mit deployt werden sollen und sich im selben Ordner befinden. Ich werde hier nicht weiter darauf eingehen, da diese bei jedem Deployment unterschiedlich sind.
Die Dateien namespace.yaml
und service-url.yaml
werden in der unten beschriebenen Pipeline erzeugt. Die Config Map für die Service URL kann einfach im Deployment mit verwendet werden.
Als Image im Deployment muss der Name aus der Kustomization (z.B. my-frontend-app
) verwendet werden. Der Image Tag wird in der Pipeline überschrieben.
Pipeline link
In meinem Fall führe ich die Automatisierung in einer GitHub-Pipeline durch. Die Schritte sollten aber auch problemlos in Gitlab funktionieren. Dazu installiere ich Kubectl und Kustomize in der Pipeline. Das mache ich über den Runtime Version Manager asdf.
Verzeichnis anlegen link
INSTANCE_NAME="pr-1234"
cd previews/
# create instance folder if not exists
mkdir -p ${INSTANCE_NAME}
# overwrite files by template
cp -a template/. ${INSTANCE_NAME}/
Preview eintragen link
Mit dem folgenden Befehl wird der Unterordner der Instance in die Datei /previews/kustomization.yaml
hinzugefügt.
kustomize edit add resource "${INSTANCE_NAME}"
Die Datei /previews/kustomization.yaml
sieht mit dem Instance Name pr-1234
wie folgt aus:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pr-1234
Namespace und URL pro Preview link
Jede Preview wird in ihrem eigenen Namespace ausgeführt und die Anwendungen (Laravel oder Phoenix) benötigen oft ihre eigene URL als Config Value.
cd ${INSTANCE_NAME}
NAMESPACE="prv-${INSTANCE_NAME}"
kubectl create namespace "${NAMESPACE}" -o yaml --dry-run=client > namespace.yaml
kustomize edit set namespace "${NAMESPACE}"
Der verwendete Befehl gibt die generierte Ressource als YAML aus und durch den Parameter –dry-run=client wird der Befehl nicht direkt auf dem Cluster ausgeführt. Dadurch werden keine Cluster Zugänge in der Pipeline benötigt und Kubernetes überwacht das Config Repository.
Die URL wird je nach Cluster-Setup unterschiedlich gesetzt. Daher gehe ich hier nur darauf ein, wie die URL einfach der Anwendung mitgeteilt werden kann. Ähnlich wie beim Namespace erstelle ich dazu eine Config Map mit dem Wert der URL. Die Config Map kann ich fest in das Deployment Template eintragen. Andere dynamische Werte können genau so hinterlegt werden.
URL="${INSTANCE_NAME}.prv.company.dev"
kubectl create configmap service-url --from-literal=url_host=${URL} -o yaml --dry-run=client > service-url.yaml
Ein Beispiel für die URL generierung mit KNative durch Annotations wäre folgendes:
kustomize edit add annotation -f "company.dev/domain-environment:prv"
kustomize edit add annotation -f "company.dev/domain-hostname:${INSTANCE_NAME}"
Falls ein Prefix vor allen Resourcen Namen gewünscht ist kann folgende Zeile ausgeführt werden.
kustomize edit set nameprefix "${INSTANCE_NAME}-"
Docker Image link
In der Kustomization (/previews/template/kustomization.yaml
) sind die Docker Images vordefiniert. Bei Anwendung der Kustomization werden alle Image-Einstellungen in den Kubernetes-Dateien mit dem Wert image: my-frontend-app
überschrieben. Somit ist es auch möglich mehrere verschiedene Images zu verwalten.
images:
- name: my-frontend-app
newName: ghcr.io/owner/repository
newTag: latest
Kustomize hat hier einen sehr praktischen und einfachen Befehl um dies anzupassen.
kustomize edit set image "my-frontend-app=${IMAGE}:${TAG}"
Annotations link
Ich habe einen Service im Cluster laufen, der Kubernetes Events überwacht und an die GitHub Deployment API zurückgibt, ob ein Deployment erfolgreich gestartet wurde. Dazu muss ich Annotations definieren. Es wäre aber auch denkbar, Labels oder Annotations für andere Automatismen im Cluster zu setzen.
kustomize edit add annotation -f "company.dev/repository_owner:company"
kustomize edit add annotation -f "company.dev/repository_name:${REPO}"
kustomize edit add annotation -f "company.dev/deployment_id:${GITHUB_DEPLOYMENT_ID}"
Deploy link
Für das Deployment müssen die geänderten Dateien nur noch in das Repository committet werden. Dies kann auch über PullRequests erfolgen.
TITLE="Deploy ${TAG} to preview/${INSTANCE_NAME} (${DEPLOYMENT_ID})"
BRANCH_NAME="preview-${INSTANCE_NAME}-${GITHUB_DEPLOYMENT_ID}"
cd ../
git checkout -b "${BRANCH_NAME}"
git add .
git commit -m "${TITLE}"
git push --set-upstream origin "${BRANCH_NAME}"
gh pr create \
--title "${TITLE}" \
--body "Deploy to ${URL}"
Komplettes Script link
Es ist sehr einfach, das Skript um weitere Variablen zu erweitern. Ich habe es im GitOps Repository als eigene Pipeline laufen. Diese wird von den Projekt Repos über die GitHub API getriggert und bekommt die entsprechenden Felder übergeben. Dadurch entsteht eine synchrone Queue, die automatisch Merge Konflikte verhindert.
Dazu kann das gleiche Skript mit der ein oder anderen if Anweisung auch für Deployments verwendet werden.
INSTANCE_NAME="pr-1234"
GITHUB_DEPLOYMENT_ID="1234567"
IMAGE="ghcr.io/owner/repository"
TAG="sdf1234"
cd previews/
# create instance folder if not exists
mkdir -p ${INSTANCE_NAME}
# overwrite files by template
cp -a template/. ${INSTANCE_NAME}/
# add instance to /previews/kustomization.yaml
kustomize edit add resource "${INSTANCE_NAME}"
# switch to instance
cd ${INSTANCE_NAME}
# create /previews/{instance_name}/namespace.yaml
NAMESPACE="prv-${INSTANCE_NAME}"
kubectl create namespace "${NAMESPACE}" -o yaml --dry-run=client > namespace.yaml
# set namespace at /previews/{instance_name}/kustomization.yaml
kustomize edit set namespace "${NAMESPACE}"
# create configmap with preview url at /previews/{instance_name}/service-url.yaml
URL="${INSTANCE_NAME}.prv.company.dev"
kubectl create configmap service-url --from-literal=url_host=${URL} -o yaml --dry-run=client > service-url.yaml
# add annotations
kustomize edit add annotation -f "company.dev/domain-environment:prv"
kustomize edit add annotation -f "company.dev/domain-hostname:${INSTANCE_NAME}"
# optional prefix all instance resources
kustomize edit set nameprefix "${INSTANCE_NAME}-"
# set image
kustomize edit set image "my-frontend-app=${IMAGE}:${TAG}"
# set annotations to report github deployment state
kustomize edit add annotation -f "company.dev/repository_owner:company"
kustomize edit add annotation -f "company.dev/repository_name:repository"
kustomize edit add annotation -f "company.dev/deployment_id:${GITHUB_DEPLOYMENT_ID}"
# create pull request
TITLE="Deploy ${TAG} to preview/${INSTANCE_NAME} (${DEPLOYMENT_ID})"
BRANCH_NAME="preview-${INSTANCE_NAME}-${GITHUB_DEPLOYMENT_ID}"
cd ../
git checkout -b "${BRANCH_NAME}"
git add .
git commit -m "${TITLE}"
git push --set-upstream origin "${BRANCH_NAME}"
gh pr create \
--title "${TITLE}" \
--body "Deploy to ${URL}"