Automatische Abläufe sind essenziell für reibungslose Entwicklungsprozesse. Jeder manuelle Eingriff ist mit potentiellen Problemen verbunden:
- menschliches Versagen
- unterschiedliche Umgebungen auf Entwickler-Computern
- externe Einflüsse (Internetverbindung, Netzwerk, usw.)
Jeder dieser Fehler ist durch einen automatischen Ablauf nahezu auszuschließen, gerade wenn es darum geht, Code-Linting / Tests durchzuführen oder neue Versionen einer Software zu bauen.
Hier kommen Pipelines von GitLab ins Spiel, ein mächtiges Tool für Entwickler.
Wir geben einen exklusiven Einblick in den Build-Prozess des Backends von unserem W71 Cloud, für das wir Pipelines in mehreren Schritten verwenden (in diesem Beispiel gehen wir davon aus, dass bereits ein funktionsfähiger GitLab Runner in GitLab konfiguriert wurde).
Um Pipelines zu konfigurieren, benötigen wir zunächst ein .gitlab-ci.yml File, das in das betreffende Repository eingecheckt wird. In dieser Datei werden die einzelnen Stages definiert, die dann jeweils als Job in der Pipeline ausgeführt werden.
In unserem Backend-Repository gibt es für den master und production Branch insgesamt fünf Stages (die ersten vier Stages werden auch für Feature-Branches gestartet):
- install
- test
- build
- build-docker
- deploy
Am Anfang der Datei definieren wir alle Stages:
stages:
- install
- test
- build
- build-docker
- deploy
Jetzt werden der Reihe nach alle Stages konfiguriert.
# == install == #
install:
stage: install
artifacts:
paths:
- node_modules/
expire_in: 1 hour
image: node:14.15.2
script:
- yarn
Da es sich bei diesem Projekt um ein nodeJS Projekt handelt, installieren wir mit dem Package-Manager yarn alle Dependencies. Damit die Dependencies später auch in den weiteren Stages verfügbar sind, definieren wir den Ordner node_modules als Artefakt mit einer Gültigkeit von einer Stunde.
# == test == #
test:
stage: test
artifacts:
paths:
- coverage/
reports:
cobertura: coverage/cobertura-coverage.xml
expire_in: 1 hour
image: node:14.15.2
script:
- yarn test:coverage
dependencies:
- install
Im zweiten Schritt führen wir die Tests durch, die es in unserem Repository gibt. Das sind vor allem Unit – oder Integrationtests. Damit die Tests auch den node_modules bekommen, nehmen wir die install-Stage als Dependency in dieser Stage auf.
Auch in dieser Stage gibt es Artefakte, nämlich die Reports aus den Tests um die Coverage berechnen zu können.
# == build == #
build:
stage: build
artifacts:
paths:
- build/
expire_in: 1 hour
image: node:14.15.2
script:
- yarn build
dependencies:
- install
Wenn die Tests durchgelaufen sind, kann die Version gebaut werden. Hier benötigen wir wieder node_modules als Dependency, als Artefakt gibt es dann den erfolgreichen Build.
# == build-docker == #
build-docker:
stage: build-docker
image: docker:stable
only:
- production
- master
- /^feature/
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN hub.m71.group
- >
if [ "$CI_COMMIT_REF_SLUG" == "production" ]; then
docker build -t hub.m71.group/w71online/w71-frontend-api:latest
else
docker build -t hub.m71.group/w71online/w71-frontend-api:$CI_COMMIT_REF_SLUG -t
fi
- docker push hub.m71.group/w71online/w71-frontend-api
dependencies:
- build
Sobald der Build vorliegt, kann nun das Docker-Image gebaut werden. Hier unterscheiden wir zwischen dem Production-Branch und den anderen. Bei einem produktiven Build taggen wir das Docker-Image mit dem Tag „latest“, ansonsten mit dem Namen des Branches. Hier gibt es kein Artefakt. Wir nutzen die Docker-Registry von GitLab und pushen unser fertiges Image dorthin.
Nach diesem Schritt liegt ein fertiges Image vor, das wir nun deployen können.
# == deploy == #
deploy:
stage: deploy
image: google/cloud-sdk
only:
- production
- master
script:
- ./deploy/rancher login $RANCHER_URL --token $RANCHER_SECRET_KEY --skip-verify
- >
if [ "$CI_COMMIT_REF_SLUG" == "master" ]; then
./deploy/rancher kubectl rollout restart deployment.apps/backend --namespace=w71-online-dev
fi
- >
if [ "$CI_COMMIT_REF_SLUG" == "production" ]; then
./deploy/rancher kubectl rollout restart deployment.apps/backend --namespace=w71-online
fi
Auch das Deployment lassen wir unsere Pipeline erledigen. Unsere Kubernetes-Plattform ist Rancher, deshalb müssen die RANCHER_URL sowie der RANCHER_SECRET_KEY in den CI/CD Optionen von GitLab hinterlegt werden. Mit dem Google Cloud-SDK können wir nun das Docker-Image austauschen, auch hier unterscheiden wir, welcher Branch gerade gebaut wurde. Die neue Version ist dann online.
Hier noch einmal unser ganzes GitLab-File:
stages:
- install
- test
- build
- build-docker
- deploy
# == install == #
install:
stage: install
artifacts:
paths:
- node_modules/
expire_in: 1 hour
image: node:14.15.2
script:
- yarn
# == test == #
test:
stage: test
artifacts:
paths:
- coverage/
reports:
cobertura: coverage/cobertura-coverage.xml
expire_in: 1 hour
image: node:14.15.2
script:
- yarn test:coverage
dependencies:
- install
# == build == #
build:
stage: build
artifacts:
paths:
- build/
expire_in: 1 hour
image: node:14.15.2
script:
- yarn build
dependencies:
- install
# == build-docker == #
build-docker:
stage: build-docker
image: docker:stable
only:
- production
- master
- /^feature/
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN hub.m71.group
- >
if [ "$CI_COMMIT_REF_SLUG" == "production" ]; then
docker build -t hub.m71.group/w71online/w71-frontend-api:latest
else
docker build -t hub.m71.group/w71online/w71-frontend-api:$CI_COMMIT_REF_SLUG
fi
- docker push hub.m71.group/w71online/w71-frontend-api
dependencies:
- build
# == deploy == #
deploy:
stage: deploy
image: google/cloud-sdk
only:
- production
- master
script:
- ./deploy/rancher login $RANCHER_URL --token $RANCHER_SECRET_KEY --skip-verify
- >
if [ "$CI_COMMIT_REF_SLUG" == "master" ]; then
./deploy/rancher kubectl rollout restart deployment.apps/backend --namespace=w71-online-dev
fi
- >
if [ "$CI_COMMIT_REF_SLUG" == "production" ]; then
./deploy/rancher kubectl rollout restart deployment.apps/backend --namespace=w71-online
fi
Über Pipelines können also ganz leicht Entwicklungsprozesse automatisiert werden und man benötigt keine weiteren Eingriffe, um neue Versionen zu bauen und online zu stellen.