https://d226lax1qjow5r.cloudfront.net/blog/blogposts/setting-up-ci-cd-with-github-actions/Blog_GitHub-Desktop_1200x600.png

Einrichten von CI/CD mit Github-Aktionen

Zuletzt aktualisiert am February 17, 2021

Lesedauer: 7 Minuten

Kontinuierliche Integration und kontinuierliches Deployment sind ein Muss für Unternehmen, die skalieren und qualitativ hochwertige Software mit hoher Geschwindigkeit bereitstellen möchten. Dieser Artikel führt Sie durch den Prozess der Verwendung von Github-Aktionen zur Erstellung von CI/CD-Abläufen.

Was ist CI/CD?

CI/CD ist ein Prozess, der die Entwicklung mit der Bereitstellung durch einen automatisierten Integrationsprozess verbindet. Die Idee dahinter ist, dass Entwickler Codeänderungen als einzigen Schritt für die Bereitstellung dieser Änderungen vornehmen können.

Die CI-Pipeline ermöglicht eine reibungslose Teamarbeit, indem sie Standards automatisiert und die Softwarequalität sicherstellt. Tools wie Linters und automatisierte Tests liefern Feedback, auf dessen Grundlage die Änderungen in den Hauptzweig integriert werden können. Diese integrierten Änderungen werden schließlich an die Endbenutzer (Produktion) ausgeliefert.

Die CD-Pipeline erhält den getesteten und genehmigten Code. Sie bestätigt, dass alle benötigten Artefakte an der richtigen Stelle bereitgestellt werden. Einige Beispiele: Bereitstellung einer Webanwendung auf einem Server, Veröffentlichung einer Bibliothek in einem Paketmanager-Repository oder Veröffentlichung einer mobilen App im App Store.

Die Automatisierung dieser Prozesse gewährleistet zwei wichtige Dinge: Der Prozess läuft schnell ab, und er ist weniger fehleranfällig.

Möchten Sie erfahren, wie Teams ihre Entwicklungs-, Integrations- und Bereitstellungsprozesse beschleunigen können? Sind Sie bereit, eine neue CI/CD-Pipeline aufzubauen? Los geht's!

Ein grundlegender CI/CD-Ablauf

  1. Verschieben Sie eine Änderung in einen Funktionszweig.

  2. Erstellen Sie einen Pull Request für diese Änderung

  3. Der Informatiker schaltet sich ein und führt die folgenden Schritte aus:

  4. Lint, Test, Build

  5. Sobald die CI abgeschlossen ist, wird der PR als gültig markiert und ein Code-Review-Prozess beginnt

  6. Wenn der PR genehmigt ist, wird der Code in den Master eingebunden.

  7. Nach der Zusammenführung setzt der CD-Prozess ein:

  8. Lint, Test, Bump Version, Bereitstellung

Wir werden ein CI/CD für eine einfache Anwendung erstellen, die Sie hier klonen können hier.

Hinzufügen eines Github-Workflows zu Ihrem Projekt

Erstellen Sie einen Ordner .github/workflows. In diesem Ordner werden wir unsere Github-Aktionen hinzufügen. Die Aktionen werden in yaml-Dateien in einer recht einfachen Struktur festgelegt, die aus drei Teilen besteht:

Name: der Name des Arbeitsablaufs, in unserem Fall Test and Build

auf: der Auslöser, der den Workflow startet, wenn die Bedingungen erfüllt sind. In unserem Fall wollen wir, dass der Workflow läuft, wenn ein Pull Request erstellt und veröffentlicht wird. Beispiele für andere Auslöser werden wir später im Tutorial sehen.

Aufträge: die eigentlichen Befehle, die im Ablauf ausgeführt werden. Dies können mehrere Aufträge sein, die parallel laufen, oder Aufträge, die voneinander abhängig sind. In diesem Beispiel gibt es einen Auftrag namens build-testder nur unter dem Betriebssystem ubuntu-latest läuft.

name: Test and Build
on:
 pull_request:
   branches:
     - main
jobs:
 build-test:
   runs-on: ubuntu-latest
   steps:
     - name: Checkout
       uses: actions/checkout@v2
       with:
         fetch-depth: 0
     - name: Setup NodeJS 14
       uses: actions/setup-node@v1
       with:
         node-version: 14
     - name: Install yarn
       run: npm install -g yarn
     - name: Install dependencies
       run: yarn install
     - name: Test
       run: yarn test

Der erste Schritt ist das Auschecken des Repositorys mit einer vorgefertigten Aktion actions/checkout@v2 aus dem Marktplatz für Aktionen. Wir übergeben die fetch-depth: 0 Variable mit der mit Eigenschaft, die dem Checkout-Prozess mitteilt, dass er das Repository mit seiner gesamten Historie abrufen soll. Die folgenden Schritte beziehen sich auf die Installation und die Ausführung von Bash-Befehlen.

Aufbau eines kontinuierlichen Integrationsworkflows

  1. Auslösen des Ablaufs mit einem Pull Request

  2. Starten Sie die Maschine

  3. Installieren Sie nodeJs auf dem Rechner

  4. Prüfen Sie das Repository

  5. Abhängigkeiten installieren

  6. Test, Lint, Build

  7. Anzeige der Ergebnisse auf der PR-Seite

Erste Schritte

  • Wenn Sie das Repository noch nicht geforkt und geklont haben, tun Sie das jetzt und erstellen Sie einen Zweig "add-ci".

  • Erstellen Sie eine Datei .github/workflows/ci.yml und kopieren Sie den Inhalt von dieser Datei in die neue Datei.

  • Commitieren Sie die Änderungen und schieben Sie sie in den neuen Zweig. Öffnen Sie eine Pull-Anfrage vom neuen Zweig zum Hauptzweig.

Wenn Sie sich den Pull-Request auf Github ansehen, sollten Sie sehen, dass die Tests laufen:

running tests

Sie werden feststellen, dass Github bereits den Namen des Workflows, den Namen des Auftrags und den Auslöser kennt. Nach der Fertigstellung sollte er wie folgt aussehen:

all checks have passed

Können Sie ein Problem in diesem Prozess erkennen?

Hinzufügen von Zusammenführungsregeln

Das erste Problem ist, dass die Pull-Anfrage zusammenführen bereits während des CI-Prozesses verfügbar war, obwohl wir die Zusammenführung erst dann ermöglichen wollen, wenn alle erforderlichen Arbeitsabläufe abgeschlossen sind.

Wir können dies in den Repo-Einstellungen beheben, indem wir zu gehen: Einstellungen>Branchen>Regel hinzufügen

branch settings

Hier wählen wir Statusprüfungen vor dem Zusammenführen bestehen lassen und überprüfen alles darunter. Sie sehen alle Arbeitsabläufe, die erforderlich sind, um die Zusammenführung zu ermöglichen - in unserem Fall haben wir nur build-test.

require status checks

Für Muster für Zweignamen einfügen main ein und erstellen Sie die Regel. Und wenn Sie zurück zur Pull-Request-Seite gehen, werden Sie sehen, dass keine Pull-Requests zusammengeführt werden können, bevor die Tests bestanden sind, es sei denn natürlich, Sie haben Admin-Rechte.

cannot merge yet

Zusammenführung nach Codeüberprüfung

Bevor wir weitermachen, müssen wir auch entscheiden, wie wir die Weitergabe von Pull Requests handhaben. Es gibt drei Hauptansätze:

  1. Erlauben Sie jedem die Zusammenführung, sobald die Tests bestanden sind

  2. Automatisches Zusammenführen nach bestandenen Tests

  3. Eine Überprüfung des Codes ist für die Zusammenführung erforderlich.

Wir entscheiden uns für die erste Option, die am häufigsten vorkommt und die wir ändern können, indem wir die gerade erstellte Verzweigungsregel bearbeiten. Am Anfang der Liste wählen Sie Pull-Anfragen vor der Zusammenführung überprüfen lassen und klicken Sie auf Speichern.

Das war's mit der kontinuierlichen Integration! Jetzt werden alle neuen Pull Requests getestet und geprüft, bevor sie in die Hauptversion eingebunden werden! Der nächste Schritt ist die Bereitstellung.

Aufbau eines kontinuierlichen Verteilungsworkflows

Das Deployment kann vom Hochladen statischer Dateien auf Github über die Veröffentlichung von npm-Paketen bis hin zum Deployment eines ganzen Microservices-Netzes alles sein. Hier ist der Prozess, den wir für unsere Anwendung befolgen werden, sobald der Code in das Haupt-Repository eingebunden ist:

  1. Abhängigkeiten installieren

  2. Test

  3. Bump-Version

  4. Bauen Sie

  5. Markieren Sie die Freigabe

  6. Bei npm veröffentlichen

  7. Bereitstellen der Demo-Anwendung

Schauen wir uns die verschiedenen Teile der yaml-Datei an, die die Aktionen handhabt:

Der Auslöser

Dies sind die Bedingungen, unter denen unser Bereitstellungscode ausgeführt wird. Er wird ausgelöst, wenn eine Pull-Anfrage geschlossen und zusammengeführt wurde.

name: Test, Build and Deploy

on:
 pull_request:
   types: [closed]

jobs:
 build-test-release:
   if: github.event.action == 'closed' && github.event.pull_request.merged == true
   runs-on: ubuntu-latest

Berechtigungen für Git-Aktionen

Im Checkout-Schritt fügen wir eine zusätzliche Zeile ein, die in der vorherigen yaml-Datei nicht vorhanden war:

steps:
  - name: Checkout
    uses: actions/checkout@v2
    with:
      fetch-depth: 0
      token: ${{ secrets.CI_REPOSITORY_ACCESS_TOKEN }}

Wir müssen das Token explizit festlegen, damit wir Dinge tun können, für die das Standard-Token, secrets.GITHUB_TOKENkeine Berechtigungen hat. In unserem Fall wollen wir eine Änderung ohne eine Codeüberprüfung an main weitergeben, also brauchen wir ein Token mit Admin-Rechten. Wir legen dieses Token unter Einstellungen -> Geheimnisse fest.

Paketversionen anheben

Dieser Schritt ist von Projekt zu Projekt unterschiedlich. Es geht darum, die Version des Pakets, das Sie im Paket-Repository veröffentlichen wollen, zu erhöhen. Dazu gehen wir alle Bibliotheken durch, die sich in diesem Zweig geändert haben, und erhöhen ihre NPM-Version:

- name: Raise version of affected libraries
  run: |
    LATEST_TAG=$(git tag -l "v*" --sort=-version:refname | head -n 1)
    LIBS=$(yarn nx affected:libs --base=$LATEST_TAG --head=HEAD --plain | awk 'NR > 2 && $1 != "Done" { print $1 }')
    for LIBRARY in $LIBS
    do
      cd ./libs/$LIBRARY
      npm version minor --no-git-tag-version --no-push
      echo "Bumping $LIBRARY"
      cd ..
      cd ..
    done
    npm version minor --no-git-tag-version --no-push

Beachten Sie, dass wir NX verwenden, ein Monorepo-Management-Framework, mit dem sich Builds und Entwicklung deutlich beschleunigen lassen. Wir verwenden das affected Funktion, die uns sagt, welche Bibliotheken von der PR "betroffen" waren.

Bauen Sie

Der Erstellungsprozess verwendet die affected Funktion ebenfalls. Er baut die geänderten Bibliotheken, die sich vom Hauptzweig aus geändert haben, mit Produktionsvorgaben:

- name: Build components
  run: yarn nx affected:build --prod --with-deps --base=main

Markieren Sie die Freigabe

Die Freischaltung erfolgt in zwei Schritten:

Die erste, get-npm-versionverwendet eine Aktion des Marktplatzes, die die Version des Hauptpakets extrahiert. Der zweite Schritt überträgt die Änderungen aus dem oben genannten Raise version Schritt vorgenommenen Änderungen, erstellt ein Tag mit der Version aus der neuen package.json und überträgt die Änderung.

Erinnern Sie sich an das CI_REPOSITORY_ACCESS_TOKEN Geheimnis? Hier kommt es ins Spiel. Da wir das Zusammenführen zu Master ohne eine Pull-Request-Prüfung verhindern, benötigen wir für diesen Teil ein Admin-Token, das es uns ermöglicht, die Regel zu umgehen und die Änderungen automatisch zu veröffentlichen.

- name: get-npm-version
  id: package-version
  uses: martinbeentjes/npm-get-version-action@master

- name: Tag the release
  run: |
    git fetch
    git config user.email "unicorn.ci@yonatankra.com"
    git config user.name "Unicorn CI"
    git add --all
    git commit -m "update versions to ${{ steps.package-version.outputs.current-version }}"
    git push

- name: Tag release
  run: |
    git tag -a v${{ steps.package-version.outputs.current-version }} -m "tag release v${{ steps.package-version.outputs.current-version }}"
    git push --follow-tags

Bereitstellen der Demo-Anwendung

Zum Schluss erstellen wir unsere Anwendung und stellen sie bereit:

- name: Build Demo
  run: yarn build:deploy

- name: Deploy 🚀
  uses: JamesIves/github-pages-deploy-action@3.7.1
  with:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    BRANCH: gh-pages # The branch the action should deploy to.
    FOLDER: dist/apps/unicorn-hunt # The folder the action should deploy.
    CLEAN: true # Automatically remove deleted files from the deploy branch

Den vollständigen yaml-Code finden Sie hier

Optimieren des CI/CD-Flusses

Caching in Github-Aktionen verwenden

Wenn Sie den CI/CD-Ablauf verfolgt und erstellt haben, werden Sie vielleicht feststellen, dass er eine Weile braucht, um zu laufen. Eine einfache Lösung dafür ist die Verwendung des Cache und die Einsparung von Zeit für die Installation von Abhängigkeiten, was wir auch mit Github-Aktionen tun können.

Die Zwischenspeicherung funktioniert, indem geprüft wird, ob ein Treffer im Cache vorliegt, und wenn dies der Fall ist, wird der Schritt zur Installation der Abhängigkeiten übersprungen. So sieht der Code aus:

- name: Install yarn
  run: npm install -g yarn

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn config get cacheFolder)"

- name: Cache yarn dependencies
  uses: actions/cache@v2
  id: yarn-cache
  with:
    path: |
      ${{ steps.yarn-cache-dir-path.outputs.dir }}
      **\node_modules
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-

- name: Install dependencies
  if: steps.yarn-cache.outputs.cache-hit != 'true'
  run: yarn install

Im ersten Schritt wird Garn global installiert. Im zweiten Schritt wird der Pfad des Garn-Cache-Ordners ermittelt. Dann verwendet der eigentliche Caching-Schritt die Cache-Aktion und übergibt die Pfade zum Cache (node_modules und den yarn-Cache-Ordner) und gibt ihm einen Cache-Schlüssel durch Hashing der Datei yarn.lock. Wenn wir auf diese Weise eine neue Abhängigkeit installieren oder eine Abhängigkeit aktualisieren, würde dies einen Cache miss. Der letzte Schritt setzt eine Bedingung für den install-dependencies Schritt unter Verwendung des Cache-Treffer Variable, die durch die Cache-Aktion gesetzt wurde.

Modularisierung des Prozesses

Das CI/CD läuft also. Es nutzt den Cache. Vorhin haben wir erwähnt, dass wir unsere beiden Bibliotheken auch in den libs Ordner in npm veröffentlichen. Technisch gesehen können wir einen weiteren Schritt zu unserem CD-Prozess hinzufügen, aber das könnte ihn umständlich und schwer zu pflegen machen.

Stattdessen haben wir den Prozess in separate Workflows aufgeteilt, die durch den CD-Hauptprozess ausgelöst werden. Der CD-Prozess würde dann eine Version erstellen, die die anderen Prozesse auslöst und die Bereitstellung und Veröffentlichung in NPM bewirkt.

Dies ermöglicht nicht nur eine bessere Wartbarkeit, sondern auch eine bessere Fehlerbehandlung. Denn nehmen wir an, die Versionierung war erfolgreich, die Veröffentlichung war erfolgreich, aber die Bereitstellung ist fehlgeschlagen. Dann können wir das Deployment einfach noch einmal ausführen und debuggen, bis es erfolgreich ist.

Den vollständigen Code für die Lösung finden Sie hier.

Zusammenfassung

In diesem Artikel haben wir einen CI/CD-Prozess mit Github-Aktionen aufgebaut.

Wir begannen mit dem Hinzufügen des CI-Prozesses, der die Tests durchführt und unseren Code-Reviewern mitteilt, dass der Pull Request für die Überprüfung bereit ist. Wir haben auch gelernt, wie man das Zusammenführen in einen Zweig blockiert, ohne dass die Tests und die Überprüfung erfolgreich sind. Wir haben uns einen vereinfachten CI-Code angeschaut, aber Sie können weitere Bash-Befehle zur Testphase hinzufügen, oder sogar weitere Schritte wie Linting oder Prettier.

Anschließend haben wir einen CD-Prozess entwickelt, der die Versionierung und das Deployment der Demo durchführt. Wir lernten, wie wir die Installation der Abhängigkeiten zwischenspeichern können, um Zeit zu sparen, und wie wir den CD-Prozess modularisieren können, um mehr Kontrolle und Wartbarkeit zu erhalten.

Es gibt so viel mehr, was man mit Github-Aktionen machen kann. Wir hoffen, dass dieses Tutorial Ihnen den Einstieg in den Aufbau eines CI/CD-Prozesses ermöglicht hat, der für Ihr Team hilfreich und sinnvoll ist.

Teilen Sie:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan KraVonage Software Architekt

Yonatan war an einigen großartigen Projekten in der Akademie und in der Industrie beteiligt - von C/C++ über Matlab bis hin zu PHP und Javascript. Früher war er CTO bei Webiks und Softwarearchitekt bei WalkMe. Derzeit ist er Softwarearchitekt bei Vonage und egghead-Dozent.