Sent from Hauptstadt!

ein Blog für den geneigten Leser

Mit Renovate Bot Abhängigkeiten aktuell halten

Tags: ,

Kategorie Software Engineering | Keine Kommentare »

In einer Microservice Welt muss man viele kleine Anwendungen pflegen. Das Aktualisieren aller Abhängigkeiten frisst ordentlich Zeit und ist eine wenig befriedigende Arbeit. Mit Renovate Bot lässt sich die Versionspflege perfekt automatisieren.

Renovate und Java Microservices

In meinem aktuellen Projekt betreuen wir knapp 10 Microservices. Jeder Service hat sein eigenes Git Repo. Natürlich versuchen wir alle Services auf den gleichen Technologiestack zu entwickeln (Java oder Kotlin und Spring Boot) und alle Services sollen möglichst jeweils die aktuellste stabile Version nutzen. Steht eine neue Hauptversion eines Frameworks an, kann die Erfahrung aus dem Upgrade eines Service so leicht auf den restlichen Zoo übertragen werden.

Trotzdem ist es Aufwand bei jedem Service die Versionen der eingesetzten Abhängigkeiten manuell zu pflegen. Bis jetzt gibt es für Maven basierte Projekte keinen eleganten Einzeiler, um alle genutzten Abhängigkeiten zu aktualisieren. In anderen Welten wie Python pip und JavaScript npm ist das besser gelöst.

Hier kommt Renovate Bot ins Spiel. Renovate versteht eine Vielzahl von Paketformaten, nicht nur aus der Java Welt, sondern auch NPM, Python aber auch Docker, etc. Nachdem Renovate die Paketinformation eingelesen und die Version aller aktuell genutzt Abhängigkeiten erkannt hat, prüft es, ob es für die Abhängigkeiten neue Releases gibt.

Falls dies der Fall ist, erstellt Renovate einen neuen Feature Branch für jede zu aktualisierende Abhängigkeit, ändert die Paketkonfiguration und erstellt nach dem Commit noch einen Pull Request.

Renovate lässt sich bis ins kleinste Detail konfigurieren. So kann ich zum Beispiel steuern, welche Paketmanager Renovate beachten soll. Aktuell lasse ich zum Beispiel in einem Java Projekt alle Maven Pakete über Renovate aktualisieren, ignoriere aber die im gleichen Git Repo enthaltenen Docker Dateien.

Renovate kann aber noch mehr als Pull Requests erstellen. Ich habe es zum Beispiel so konfiguriert, dass es die Pull Requests automatisch mergt, wenn

  • es inzwischen einen erfolgreichen Bau gibt
  • und es sich bei der aktualisierten Abhängigkeit lediglich um ein Patch Release handelt.

Auch die von Renovate Bot erstellten Pull Requests sind sehr hilfreich. Falls im Netz verfügbar, fügt Renovate in den Pull Request auch den Changelog aller Änderungen ein. Es ist einfach ein geniales Werkzeug!

Renovate mit Jenkins ausführen

Es gibt verschiedenste Möglichkeiten, um Renovate auszuführen oder zu hosten. Ich könnte zum Beispiel einen Docker Container deployen, der periodisch alle ihm bekannten Repos absucht.

Momentan nutzen wir in unserem Projekt ein anderes Vorgehen. Wir starten Renovate stündlich per Jenkins. Neben Jenkins setzen wir eine firmeninterne Instanz von Bitbucket ein. Java Abhängigkeiten sind über ein internes Nexus Repository verfügbar.

Hier der prinzipielle Aufbau der Jenkinsfile:

#!/bin/groovy

pipeline {
    agent { label 'docker'}
    triggers {
        cron(env.BRANCH_NAME == 'main' ? '17 * * * *')
    }

    options {
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '15', artifactNumToKeepStr: '3'))
        preserveStashes(buildCount: 3)
    }

    stages{
        stage('check repos for new versions') {
            steps {
                withCredentials([
                    string(credentialsId: 'github-renovate-bot-token', variable: 'GITHUB_API_TOKEN'),
                    string(credentialsId: 'bitbucket-renovate-bot-token', variable: 'BITBUCKET_API_TOKEN')
                    usernamePassword(
                        credentialsId: 'nexus-credentials', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_PASSWORD'
                        )
                ]) {
                    sh 'wget http://interner-server/internes_ssl_zertifikat.crt'
                    sh 'docker run --rm 
                    -v "${WORKSPACE}/renovate.config.js:/usr/src/app/config.js" 
                    -v "${WORKSPACE}/internes_ssl_zertifikat.crt:/usr/src/app/internes_ssl_zertifikat.crt" 
                    -e GITHUB_COM_TOKEN=$GITHUB_API_TOKEN 
                    -e BITBUCKET_TOKEN=$BITBUCKET_API_TOKEN 
                    -e NEXUS_USER=$NEXUS_USER 
                    -e NEXUS_PASSWORD=$NEXUS_PASSWORD 
                    -e NODE_EXTRA_CA_CERTS=/usr/src/app/internes_ssl_zertifikat.crt 
                    renovate/renovate:slim'
                }
            }
        }
    }
}

Die Pipeline startet jede Stunde zur 17. Minute und wird auf einem Agent mit Docker Unterstützung ausgeführt. Ich behalte die Logs nur einiger Ausführungen. Die gesamte Pipeline besteht aus nur einer Stage.

Damit Renovate Bot seine Aufgabe erfüllen kann, benötigt es diverse Zugänge. In diesem Beispiel lade ich aus dem Jenkins Credential Store:

  • API Token für den Zugriff auf einen internen Bitbucket Server
  • API Token für den Zugriff auf GitHub (ohne Token würde der Zugriff durch GitHub ziemlich schnell geblockt werden)
  • Nutzername und Passwort für den Zugriff auf das Nexus Repository

Etwas weiter unten zeige ich, wie die Umgebungsvariablen genutzt werden.

Die Stage besteht im Prinzip nur aus dem Starten des Renovate Containers. Zuvor lade ich noch von einem internen Server ein privates SSL Zertifikat runter und mounte dieses per Volume in den Container, damit der Zugriff auf die internen Nexus und Bitbucket Server klappt.

Ich habe in das docker run Kommando mehrere Zeilenumbrüche für eine bessere Lesbarkeit eingefügt. Die zuvor zusammengetragenen Daten werden nun an Renovate per Umgebungsvariablen übergeben. Die Renovate Doku gibt Auskunft über unterstützte Variablen.

Ich mounte auch 2 Volumes in den Docker Container:

  • das firmeninterne SSL Zertifikat
  • eine Konfigdatei für Renovate

Hier die verwendete Renovate Konfigurationsdatei renovate.config.js:

module.exports = {
    platform: "bitbucket-server",
    username: "jenkins",
    password: process.env.BITBUCKET_TOKEN,
    endpoint: "https://bitbucket.intern.example.com",
    repositories: [
        "projektA/repo1",
        "projektA/repo2",
        "projektB/repo7",
        "projektC/repo4"
    ],
    onboarding: true,
    logLevel: "DEBUG",
    dryRun: false,
    autodiscover: false,
    hostRules: [
        {
            hostType: "docker",
            matchHost: "https://docker-repo.intern.example.com"
        },
        {
            hostType: "maven",
            matchHost: "https://nexus.intern.example.com",
            username: process.env.NEXUS_USER,
            password: process.env.NEXUS_PASSWORD,
        },
    ],
};

In dieser Konfigurationsdatei tauchen auch wieder einige der zuvor gesetzten Umgebungsvariablen auf, um zum Beispiel Nutzername und Passwort für den Zugriff auf Nexus zu setzen.

Wichtig ist die repositories Liste. In dieser Liste sind alle Bitbucket Repositories aufgelistet, um die sich Renovate Bot kümmern soll. Möchte ich die Abhängigkeiten in einem weiteren Git Repository durch Renovate aktuell halten lassen, füge ich es einfach in diese Liste ein und es wird sofort bei der nächsten Ausführung berücksichtigt.

An diesem Punkt wird Renovate stündlich ausgeführt und weiß, welche Repositories es aktuell halten soll. Renovate hat alle benötigten Zugänge für den Zugriff auf Bitbucket, interne Artefakt Repos wie Nexus und für den öffentlichen Zugriff auf das GitHub API. Jetzt fehlt nur noch die Konfiguration pro Repository.

Renovate für Bitbucket Repo anpassen

Wir verwenden Renovate für verschiedene Teams, die unterschiedliche Strategien zum Umgang mit Versionsaktualisierungen verfolgen. Jedes Team soll selbständig für seine Repos entscheiden können, welche Paketmanager Renovate beachtet, welche neue Versionen überhaupt vorgeschlagen werden sollen und ob ein automatischer Merge gewünscht ist.

Standardmäßig nutzt Renovate beim Zugriff auf ein Bitbucket Repo den konfigurierten Entwicklungsbranch, also etwa develop. In diesem Branch sucht es auf oberster Ebene nach einer renovate.json5 Konfigurationsdatei, die in meinem Fall folgendermaßen aussieht:

{
    "$schema": "https://docs.renovatebot.com/renovate-schema.json",
    "packageRules": [
        {
            "matchUpdateTypes": [
                "patch",
            ],
            "automerge": true,
        }
    ],
    "enabledManagers": [
        "maven"
    ],
    "rollbackPrs": true
}

Über enabledManagers erlaube ich Renovate lediglich Maven Paketdateien (pom.xml) zu analysieren.

Interessant sind die packageRules. Hier kann ich über Matching-Regeln eine spezielle Konfiguration setzen, die nur dann gilt, wenn alle Regeln erfüllt sind.

Im obigen Beispiel definiere ich, dass ich die Option automerge aktivieren möchte, wenn es sich bei der neuen Version lediglich um ein Patch Versionsupgrade handelt (major.minor.patch). In Bitbucket habe ich unabhängig von Renovate konfiguriert, dass der Merge eines Feature Branch nur möglich ist, wenn es ein erfolgreiches Build gibt.

Nachdem Renovate also einen Feature Branch mit der geänderten Maven Paketdatei angelegt hat, läuft unabhängig von Renovate die Baupipeline los. War dieses Build erfolgreich, so wird der Status des letzten Builds für diesen Branch durch Jenkins in Bitbucket hinterlegt. Bei seinem nächsten stündlichen Besuch sieht Renovate, dass der Build erfolgreich war und mergt in diesem Fall den Pull Request automatisch.

Renovate kann noch viel mehr

Renovate Bot ist wie ein perfektes Uhrwerk und begeistert durch die vielen kleinen genialen Details:

  • Es wird pro neuer Version immer nur ein Branch angelegt. Dieser wird mit allen Änderungen aus seinem Upstream Branch (meist develop) stets von Renovate aktualisiert, falls dies konfliktfrei möglich ist.
  • Lehne ich einen von Renovate erstellten Pull Request ab, schlägt Renovate diese Version nicht erneut vor.
  • Bei sehr umfangreichen Projekten kann Renovate einen Meta Pull Request erstellen und pflegen, in dem man alle aktuell noch zu bearbeitenden Pull Requests sieht. Dieses so genannte Renovate Dashboard hält Renovate selbst aktuell.

Ich habe noch nicht ansatzweise alle möglichen Optionen durchdacht oder sogar ausprobiert. Selbst mit diesem relativ einfachen Setup hat mir Renovate Bot in den letzten Monaten schon mehrere Tage Arbeit erspart!

Schreiben sie ein Kommentar