Effizientes Dependency Management für Node.js Microservices mit Renovate

Mark Schmeiser

In der Welt der Softwareentwicklung ist die Aktualisierung von Abhängigkeiten ein entscheidender Schritt für die Aufrechterhaltung eines robusten und sicheren Codebase. Dies gilt insbesondere für Node.js Microservices, bei denen regelmäßige Updates eine Herausforderung darstellen können.

In diesem Blogbeitrag wollen wir einen effizienten Ansatz für das Handling von Abhängigkeiten in Node.js Microservices vorstellen, wobei wir auf das leistungsstarke Tool Renovate setzen, um den Prozess zu automatisieren.

Die Herausforderung

In unseren Projekten stehen wir regelmäßig vor der Frage, wie wir Abhängigkeiten aktualisieren können, insbesondere bei unseren Node.js Microservices. Unser Ziel ist es, den manuellen Aufwand zu minimieren und die Abhängigkeiten stets aktuell zu halten. Während dies für Backend-Node.js-Services mit akzeptabler Testabdeckung gut funktioniert, hatten wir im Frontend weniger gute Erfahrungen. Hier traten Rendering-Probleme auf, die weder durch Snapshots noch durch andere Tests gefunden werden konnten.

Unser Setting

Unsere Infrastruktur umfasst:

  • Buildautomatisierung mit GitHub Actions
  • Das Buildartefakt ist ein Docker-Image in der GitHub Registry
  • Tests werden mit Jest durchgeführt
  • Die Services sind in Node.js mit TypeScript geschrieben

Bei einem Update müssen verschiedene Aspekte berücksichtigt werden, darunter das Dockerfile für die LTS-Version von Node.js, die Versionierung von GitHub Actions, NVM und die darin referenzierte Node-Version, sowie das package.json mit den npm-Paketen für Entwicklung und Produktion.

Die Lösung: Renovate im Einsatz

Als ausgezeichnetes Tool für das Dependency Management hat sich Renovate herausgestellt. Das folgende Setup hat sich besonders bewährt:

  1. Basis-Settings in einem separaten Repository:
    • Einrichtung eines dedizierten Repositories mit den Basiseinstellungen für Renovate.
    • Konfiguration einer renovate.json5Datei in jedem Service-Repository, die auf die Basiseinstellungen verweist.
{
  "$schema": "",
  "extends": [
    "github>{ORGANIZATION_NAME}/{BASE_SETTING_REPO_NAME}:preset-backend.json5"
  ]
}
  1. Automatisches Mergen für Non-Major Updates:
    • Konfiguration von Renovate, um Non-Major-Updates automatisch zu mergen, wenn die Tests erfolgreich durchlaufen.
    • Dies spart erheblich Zeit und beschleunigt den Update-Prozess.
  2. Integration mit GitHub Actions:
    • Aktivierung des Auto-Mergings in den GitHub-Einstellungen, wobei das Bestehen von Status-Checks vor dem Mergen erforderlich ist
      • Github Settings / General → Allow auto-merge anklicken
      • Github Settings / Branches / Select branch → Edit
        • Require status checks to pass before merging anklicken
        • Require branches to be up to date before merging anklicken
        • Steps aus dem Workflow suchen, die als Status checks verlangt werden
    • Erlaubnis für GitHub Actions, Pull Requests zu erstellen und zu genehmigen.
      • Github Settings / Actions / General → Allow GitHub Actions to create and approve pull requests anklicken
  3. Renovate Basiseinstellungen:
    • Ableitung von den empfohlenen Standards.
{
  "$schema": "",
  "extends": [
    "config:recommended"
  ],
}
  • Planung der Checks zu Beginn der Woche, um die Build-Minuten zu minimieren.
"schedule": "before 8am on the first day of the week",
"timezone": "Europe/Berlin",
"vulnerabilityAlerts": {
  "labels": [
    "security"
  ],
  "schedule": "at any time"
},
  • Aktivierung des Auto-Mergings mit speziellen Commit-Einstellungen.
    "automerge": true,
    "automergeType": "branch",
    "automergeStrategy": "rebase",
      "rebaseWhen": "behind-base-branch",
      "platformAutomerge": true,

      // Keep package-lock.json updated, similar to `npm audit fix`
      "lockFileMaintenance": {
        "enabled": true
      },

      // define commit
      "commitMessagePrefix": "chore(deps): ",
      "commitBodyTable": true,
      "platformCommit": true,

      // dashboard settings
      "dependencyDashboard": true,
      "dependencyDashboardAutoclose": true,
      "configMigration": true,

      // When updating, bump version ranges (e.g. ^0.1.3 -> ^0.1.4)
      "rangeStrategy": "bump",
  • Ausschluss des Auto-Mergings für Major Updates, die separat überprüft werden.
"major": {
  "automerge": false,
  "dependencyDashboardApproval": true,
    "commitMessagePrefix": "chore(deps-major): ",
    "labels": [
      "dependencies",
      "breaking"
    ]
},
  • Bündelung von Non-Major-Updates und Beschränkung von Node.js-Versionen auf aktuelle LTS-Versionen.
"packageRules": [
  // bundle all non-major updates and automerge them
    {
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "automerge": true,
      "groupName": "non-major dependencies",
      "groupSlug": "minor-patch"
    },

    // Keep Node.js (.nvmrc) updated to maintained versions - meaning even version numbers
    {
      "matchDepNames": [
        "node"
      ],
      "major": {
        "enabled": true
      },
      "allowedVersions": "^20 || ^22",
    },
],
  • Möglichkeit, bestimmte Abhängigkeiten zu ignorieren, die Probleme verursachen könnten.
"ignoreDeps": [
  // ... (Spezifizieren Sie zu ignorierende Abhängigkeiten)
]

Fazit

Die Implementierung eines effizienten und automatisierten Prozesses für das Dependency Management ist entscheidend für Node.js Microservices. Renovate bietet mit seinen leistungsstarken Funktionen und anpassbaren Einstellungen eine robuste Lösung für diese Herausforderung. Durch die Umsetzung des skizzierten Setups wird im Projekt viel Zeit gespart, manuelle Arbeit minimiert und sichergestellt, dass die Services stets auf dem neuesten Stand und somit sicher(er) sind.