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.

Kategorien: Technik