Eine GitLab CI/CD-Pipeline, die für die meisten kleinen Projekte reicht
Werbung
Wenn ich ein kleines Projekt übernehme, das noch überhaupt kein CI/CD hat, bedeutet Deployment meistens: Jemand verbindet sich per SSH, zieht den neuesten Commit und startet einen Service von Hand neu.
Das funktioniert genau so lange, bis es nicht mehr funktioniert. Ein vergessenes npm install, ein Deploy vom falschen Branch oder eine fehlende Umgebungsvariable reichen völlig aus, um die Live-Umgebung lahmzulegen. Die Lösung dafür ist keine überkomplexe Pipeline mit einem Dutzend Custom-Stages. Die Lösung ist eine kurze .gitlab-ci.yml, die extrem zuverlässig exakt dieselben drei Dinge tut: Build, Test und Deploy.
Der Aufbau
stages:
- build
- test
- deploy
variables:
GIT_DEPTH: 1
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:
stage: test
image: node:20
script:
- npm ci
- npm run test -- --ci
GIT_DEPTH: 1 hält Git-Checkouts auch bei Repositories mit ewig langer Historie unglaublich schnell. Das branch-basierte Caching von node_modules sorgt dafür, dass die meisten Pipeline-Durchläufe eine komplette NPM-Neuinstallation überspringen können. Die Build-Stage erzeugt ein kompiliertes Artifact, das die Deploy-Stage anschließend wiederverwendet. Das garantiert, dass der Code pro Pipeline-Lauf nur ein einziges Mal gebaut wird und nicht in jeder Stage erneut.
Deploying über SSH
Für kleine Projekte ist das Aufsetzen eines Kubernetes-Manifests oder einer Container-Registry oft deutlich mehr Infrastruktur-Overhead, als das Projekt überhaupt braucht. Simples rsync über SSH, getriggert aus der CI, deckt eine erstaunlich große Menge an echten Deployments sicher ab:
deploy_staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$STAGING_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$STAGING_HOST" >> ~/.ssh/known_hosts
script:
- rsync -az --delete dist/ deploy@$STAGING_HOST:/var/www/staging/
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
Der SSH-Schlüssel liegt streng getrennt in einer maskierten, geschützten CI/CD-Variablen, niemals im Repository selbst. Der ssh-keyscan-Befehl füllt die known_hosts-Datei, damit der Pipeline-Job nicht endlos an einer Host-Key-Bestätigung hängen bleibt. Zu guter Letzt sorgt rsync --delete dafür, dass das Remote-Verzeichnis exakt mit dem Build-Output synchronisiert wird, anstatt über die Zeit langsam mit Dateileichen vollzumüllen.
Ein manuelles Gate für Production
Staging wird bei jedem Push auf den develop-Branch automatisch deployt. Production deployt vom main-Branch, aber ausschließlich dann, wenn jemand explizit auf den Knopf drückt:
deploy_production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$PRODUCTION_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$PRODUCTION_HOST" >> ~/.ssh/known_hosts
script:
- rsync -az --delete dist/ deploy@$PRODUCTION_HOST:/var/www/production/
environment:
name: production
url: https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Das when: manual verwandelt den Job in der Pipeline-Ansicht von GitLab in einen echten Button, anstatt ein Skript zu sein, das automatisch abfeuert. In Kombination mit dem Environment-Tracking von GitLab erhält man so außerdem eine vollständige Deployment-Historie mit 1-Klick-Rollback auf jedes beliebige vorherige Artifact. Dieser Rollback-Button hat schon mehr als einmal ein fehlgeschlagenes Deployment in einen 30-Sekunden-Fix verwandelt, anstatt in absolute Panik auszuarten.
Warum das fast immer ausreicht
Diese Pipeline lässt keinen Linter laufen. Sie baut kein Docker-Image. Sie spricht nicht mit Kubernetes. Und für einen massiven Anteil kleinerer Projekte ist das völlig in Ordnung. Diese Dinge können jederzeit später ergänzt werden, falls das Projekt wächst und sie wirklich benötigt.
Was diese Pipeline jedoch sofort leistet: Sie eliminiert dauerhaft die drei typischen Fehlerquellen manueller Deployments. Jemand vergisst Abhängigkeiten zu installieren, jemand deployt ungetesteten Code oder jemand bringt den falschen Branch live. Eine .gitlab-ci.yml dieser Größe fängt alle drei Fälle sicher ab. Und sie ist kurz genug, dass der nächste Entwickler, der das Projekt anfasst, den kompletten Deployment-Flow in unter einer Minute lesen und verstehen kann.
Werbung