Continuous Integration von Hyperledger-Composer-Anwendungen mit Gitlab CI

Keine Kommentare

In meinem vorherigen Artikel „Hyperledger-Fabric-Test-Netzwerk mit Ansible auf AWS aufsetzen“ habe ich eine einfache Möglichkeit vorgestellt, VM-Instanzen in der Cloud mittels Ansible mit der nötigen Software zu provisionieren, um eine Hyperledger-Fabric- und Composer-Test-Umgebung zu erstellen. Unter der Annahme, dass diese Umgebung nun bereit ist, Business Network Archives (BNAs) zu installieren und anzubieten, möchten wir kontinuierlich die Code-Änderungen dieser Anwendung testen, bauen und ausliefern. Zur Definition solcher Pipelines eignet sich Gitlab CI hervorragend, daher möchte ich vorstellen, wie eine rudimentäre Pipeline-Implementierung damit aussehen könnte. Continuous Integration & Hyperledger schließen sich mit der richtigen Herangehensweise und den richtigen Tools keinesfalls gegenseitig aus.

Konzeption einer Pipeline für Hyperledger Composer BNAs

Am Anfang steht die Konzeption der zu implementierenden Pipeline. Eine klassische und simple Abfolge für eine mit JavaScript implementierte Anwendung, die getestet, installiert und gebaut werden soll, kann so aussehen:

  1. Dependency- und Code-Security-Checks mit RetireJS und NPM Audit
  2. Installieren der NPM-Dependencies aus package.json
  3. Überprüfung von Einhaltung der Coding Guidelines (Linting)
  4. Ausführung von Unit Tests
  5. Ausführung von BDD-Tests
  6. Bauen der BNA und Installation/Update auf der Ziel-Umgebung
  7. Erhöhen der Minor-Version in einem CI-Commit sowie Git Tagging

An einer Demo-Anwendung auf Github aus einem meiner früheren Artikel „Implementierung einer Blockchain-Anwendung mit Hyperledger Fabric und Composer“ können wir uns zur praktischen Umsetzung der Pipeline orientieren. In dem Repository der Anwendung wird der Branch „gitlab-ci-pipeline“ angelegt, der die Umsetzung dieser Pipeline demonstriert. Bevor wir uns Schritt für Schritt der angestrebten Pipeline nähern, sollten wir jedoch erstmal schauen, ob wir das alles problemlos umsetzen können.

In der package.json der Demo-Anwendung sind schon einige Scripts definiert, die wir für die einzelnen Pipeline-Schritte (Jobs) benötigen. Auch NPM Audit, den seit Neuestem von Node integrierten Dependency-Security-Checker, erhalten wir ohne weiteres Zutun. RetireJS müssen wir uns mit NPM zusätzlich installieren. Funktionalität von Composer können wir über die lokale Installation in ./node_modules/.bin/composer aufrufen. Für die Erstellung des Git Version-Bump Commits und Tags können wir NPM nutzen, das uns „npm version patch“ zur Verfügung stellt. Für den benötigten Download der Composer PeerAdmin-Card, die für das Deployment bzw. den Zugriff mit der PeerAdmin-Rolle auf das Netzwerk benötigt wird, ist praktischerweise wget im Node-Docker-Image vorhanden. Wir verwenden die aktuelle LTS-Version von Node (8.12.0) im Code (forciert durch .nvmrc im Repository), daher nutzen wir im Gitlab-Build auch dasselbe Docker-Image, da es die benötigten NPM Tools in der richtigen Version mitbringt.

Außerdem praktisch an Gitlab CI ist, dass unsere Jobs mithilfe von Stages auch parallelisiert werden können. Die Security- und Dependency-Checks können mit der Installation der NPM-Dependencies parallel laufen. Danach können Linting und unterschiedliche Tests ebenfalls parallel ausgeführt werden, ohne sich gegenseitig zu beeinflussen. Das führt dazu, dass die gesamte Laufzeit der Pipeline minimiert wird, aber Abhängigkeiten der Jobs trotzdem eingehalten werden. Wenn alle Jobs vor dem Deployment erfolgreich waren, kann das Deployment mit anschließendem Tagging den Pipeline-Lauf abschließen. Die Stages nennen wir daher wie folgt: „Security & Setup“, „Test“ und „Deploy & Release“.

Implementierung der Pipeline

Es wurde sichergestellt, dass die Wunsch-Pipeline ohne Umwege über lästiges Scripting umgesetzt werden kann. Das Konzept in Form von Schritten und Stages steht und es besteht sogar die Aussicht, dass sich viel parallelisieren lässt. Daher beginnen wir ganz am Anfang, beim Grundgerüst der Pipeline-Definition für Gitlab CI, und legen im Gitlab-Repository unserer Anwendung .gitlab-ci.yml an.

Für das Caching der meist sehr schweren node_modules von NPM bietet sich das Caching-Feature von Gitlab CI hervorragend an. Am Ende der Pipeline wird Gitlab dafür sorgen, dass die angegebenen Pfade vorgehalten werden. Bei den folgenden Jobs und zukünftigen Läufen der Pipeline werden die Verzeichnis-Strukturen aus dem Cache wiederhergestellt und sparen uns kostbare Zeit und Nerven, da alle Dependencies durch das Vorhandensein von node_modules installiert sein werden.

Die Datei soll initial so aussehen:

image: node:8.12.0 # Wir nutzen das Node-Image im gesamten Build

cache:
  paths:
    - node_modules/

variables:
  COMPOSER_CMD: "./node_modules/.bin/composer"

stages:
  - security-and-setup
  - test
  - deploy-and-release
 
# Alle Job-Definitionen werden hier nun der Reihe nach auf der Root-Ebene aufgelistet, ein Beispiel:
# my-first-job:
#   stage: security-and-setup
#   script: npm run some-task
#   (Benötigt ist nur "script", Jobs verfügen aber noch über viele weitere nützliche Features: https://docs.gitlab.com/ee/ci/yaml/#jobs)

Wir beginnen mit der Implementierung der ersten Stage und legen dafür wie geplant drei Jobs an: NPM Audit Security-Check, RetireJS Security-Check und einen zur Ausführung von NPM Install. An der im letzten Code-Listing kommentierten Stelle (nach Definition der Stages) beginnen wir damit:

install-dependencies:
  stage: security-and-setup
  script: npm install --progress=false

npm-audit-security-check:
  stage: security-and-setup
  script: npm audit

Diese zwei Jobs lassen sich einfach lesen und verstehen. Die Dependencies werden installiert und parallel auf bekannte Sicherheitslücken geprüft. Am Ende von install-dependencies wurde node_modules angelegt bzw. aktualisiert.

Als nächstes wartet die Implementierung der Test-Stage mit den Jobs für Retire, Linting, Unit-Tests und BDD-Tests. Auch hier lernt man als JavaScript-Entwickler leider nicht mehr viel Neues:

retire-security-check:
  stage: test
  script: ./node_modules/.bin/retire -p
  dependencies:
    - install-dependencies

lint:
  stage: test
  script: npm run lint
  dependencies:
    - install-dependencies

unit-tests:
  stage: test
  script: npm run test:unit
  dependencies:
    - install-dependencies

bdd-tests:
  stage: test
  script: npm run test:bdd
  dependencies:
    - install-dependencies

Die Test-Jobs sind von install-dependencies abhängig, da zur Ausführung dieser auch die installierten Dependencies benötigt werden. Die Unit- und BDD-Test sowie Linting-Jobs werden somit beginnen parallel zu laufen, sobald die Abhängigkeiten erfüllt wurden.

Bisher wirkt alles sehr vertraut, wenn man gelegentlich im JavaScript- und NPM-Universum unterwegs ist. Im nächsten Schritt kümmern wir uns darum, eine Verbindung zum Hyperledger Test-Netzwerk aufbauen zu können, den Version-Patch-Commit- und Tag zu erstellen, das Business Network Archive zu bauen und es auf dem Netzwerk zu installieren.

patch-deploy-publish:
  stage: release-and-deploy
  before_script:
    - wget ${COMPOSER_DEV_CARD_LOCATION}
    - ${COMPOSER_CMD} card import --file fabric-dev-peer-admin.card
    - ${COMPOSER_CMD} card list
  script:
    - git config user.name "Gitlab CI" && git config user.email "some-product-team@codecentric.de"
    - NEW_APP_VERSION="$(npm version patch -m '[skip ci] patch version upgrade')"
    - npm run archive:create
    - ${COMPOSER_CMD} network install -a dist/engine-supplychain.bna --card PeerAdmin@hlfv1
    - ${COMPOSER_CMD} network start -n engine-supplychain -V ${NEW_APP_VERSION/v/} --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv11 --file local-network-admin.card || ${COMPOSER_CMD} network upgrade -c PeerAdmin@hlfv1 -n engine-supplychain -V ${NEW_APP_VERSION/v/}
  after_script:
    - git push https://mygitlabname:${PRIVATE_ACCESS_TOKEN_MYGITLABNAME}@gitlab.com/${CI_PROJECT_PATH}.git HEAD:master --follow-tags
  only:
    - master
  artifacts:
    paths:
      - dist/engine-supplychain.bna
      - local-network-admin.card
    expire_in: 2 week

Hier geschieht ein bisschen mehr als in den vorherigen Jobs. Zuerst wurde entschieden, dass Releasing und Deployment nur auf dem Master-Branch ausgeführt wird. Auf anderen Branches, ggf. weil mit Feature-Branches gearbeitet wird, werden nur die ersten zwei Stages ausgeführt. Damit dieser Job funktioniert, ist es wichtig, zwei spezielle Build-Variablen in Gitlab für das Projekt anzulegen. Diese sollten als „protected“ markiert sein, wenn unser Master Branch protected ist, damit andere Branches diese Secrets nicht nutzen können. Eine Komplexität in diesem Job, die etwas Erklärung bedarf, ist der unterschiedliche Umgang mit erstmals im Gegensatz zu bereits deployten BNAs. Wurde eine BNA bereits in einer Version gestartet, muss composer upgrade genutzt werden, um auf die neu installierte Version zu wechseln. Initial muss composer start genutzt werden, um das installierte BNA zu starten. Dies wird mit dem Oder-Operator gelöst, der den Update-Befehl nutzt, falls der Start-Befehl zu keinem erfolgreichen Exit-Code führt.

Unter COMPOSER_DEV_CARD_LOCATION muss ein Pfad zum Download der PeerAdmin@hlfv1 Composer-Card gespeichert werden. Die Karte beinhaltet Verbindungs- und Authentifizierungs-Daten gegen das Hyperledger Test-Netzwerk, das wir provisioniert haben. Die Karte wurde beim Aufsetzen der Umgebung durch das Ansible Playbook erzeugt, auf den Ansible-Host heruntergeladen und in den lokalen Card-Store importiert. Dort können wir sie also finden und z.B. über einen Amazon S3 Bucket verfügbar machen. Für die Benutzung in einem echten Projekt sollte man sich noch Gedanken machen, wie man den Download-Link der Karte mit Authentifizierung schützt und wie sich das Pipeline Script authentifizieren kann. Um die Variable PRIVATE_ACCESS_TOKEN_MYGITLABNAME im Job verfügbar zu machen, muss für unseren (oder einen anderen Nutzer) ein privater Access Token in Gitlab generiert werden. Den Token legen wir in dieser geschützten Variable ab. Git nutzt den Token in diesem Job, um im Namen des Token-Erstellers den Push auszuführen. Nach dem Push ist der Version-Tag und Commit auf dem Master-Branch verfügbar. Die Pipeline wird jedoch nicht nochmal ausgeführt, da der String „skip ci“ Gitlab signalisiert, dass der Commit übersprungen werden soll.

Wenn der Job erfolgreich beendet wurde, wird das gebaute BNA und die Composer-Card des local network admins als Artefakt für zwei Wochen aufbewahrt.

Continuous Integration und Hyperledger – Das Resultat

Wie vorgestellt, kann mit Gitlab CI eine mächtige Pipeline im YAML-Format in grade mal 70 Zeilen Code erstellt werden. Die Features von Gitlab CI lassen für diesen Anwendungsfall keinen Wunsch übrig und funktionieren auf jeder aktuellen Installation – ob (sogar im Free-Plan) auf Gitlab.com oder der eigenen On-Premise-Installationen. Die Interaktion mit Composer stellt sich als problemlos heraus, da Composer-CLI ein NPM-Modul ist, das bereits in den Dev-Dependencies des Projektes angefordert wird. Da die Composer-Card alle Details bündelt, die benötigt werden, um von außen mit einem Composer-Netzwerk zu kommunizieren, ist kein lästiges Scripting erforderlich, um das Deployment zu implementieren. Für den Fall, dass das Deployment doch mal schief läuft, wird der Git-Push von Version-Tag und Commit trotzdem durchgeführt. Die resultierende Pipeline können wir uns in Gitlab anschauen. Da das Demo-Repository nicht auf Gitlab gehostet ist und dafür auch kein Test-Netzwerk provisioniert wurde, hier ein Beispiel.

Die Pipeline läuft in knapp fünf Minuten durch – das kann sich sehen lassen! Die Security-Checks wurden mit einer Warnung versehen, da sie fehlschlugen. In der Pipeline-Definition, aus der der Screenshot stammt, wurden diese Jobs allerdings mit allow_failure: true versehen, um einen erfolgreichen Build trotz teils veralteter Dependencies zu provozieren. Zum jetzigen Punkt haben wir eine lauffähige Test-Umgebung mit Hyperledger Fabric und Composer Playground, die wir kontinuierlich mit getesteten Änderungen und Features bespielen können. Wie sich der Code während des Einbauens der Gitlab CI-Pipeline geändert hat, kann man in diesem Pull-Request in der Vergleichs-Ansicht auf dem Repository sehen.

Wie im vorherigen Artikel erläutert, ist das Aufsetzen, Betreiben und Entscheiden über eine Produktionsumgebung mit Hyperledger noch mit vielen Fragen und Herausforderungen verbunden. Hier ist die Industrie noch weit von einem Konsens über Best Practises, Tools und rechtlichen Rahmenbedingungen entfernt. Wenn das Zusammenspiel dieser Faktoren etwas gereift ist, wird es natürlich auch hierzu einen Blog-Artikel geben.

Jonas Verhoelen

Jonas brennt dafür, in hoher Geschwindigkeit mehr Geschäftswert durch gute Software zu schaffen. Er entwickelt am liebsten in Type- oder JavaScript sowie verschiedenen JVM-Sprachen und arbeitet dabei gerne full-stack. Zudem steht er dem Kunden mit Expertenwissen, Tools und Methoden zur Beratung rund um Distributed Ledger Technology und IT Security zur Verfügung. Teamwork, Selbstorganisation und ein agiles Mindset sind die Basis seiner täglichen Arbeit.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.