Skip to main content

Kontinuierliche Dependency-Updates: Prozesse verbessern, indem man den Aufwand vorzieht

Artikel von
Headshot of James Bowes

James Bowes

24. Mai 2021

0 Min. Lesezeit

Dies ist eine Geschichte darüber, den Aufwand vorzuziehen, um Verzeihung zu bitten und sich kontinuierlich zu verbessern. In den Anfangstagen von Manifold – lange bevor wir zu Snyk kamen – entwickelten wir einen unabhängigen Marktplatz für Entwickler-Services (wie Datenbanken oder Anbieter für Transaktions-E-Mails). Unsere Code-Struktur war typisch: eine React-Frontend-App und eine Sammlung von Go-Microservices, die mit einer Datenbank kommunizierten. Eine typische Struktur bedeutete auch typische Probleme. Eines davon war, unsere Dependencies aktuell zu halten. Als erfahrenes Teammitglied, das lange genug im Unternehmen war, um viele unserer Probleme mitverursacht zu haben, machte ich mich daran, unsere Dependency-Probleme zu lösen – mit der Unterstützung einiger bereitwilliger Kolleginnen und Kollegen.

„Dependencies zu aktualisieren, ist eine Menge Arbeit!“

Über die Jahre und hinweg über Programmiersprachen und Frameworks hinweg ist die Klage über das Aktualisieren von Dependencies ein Dauerbrenner. Wenn Sie kein Update benötigen, um ein neues Feature zu entwickeln oder einen neuen Bug zu beheben – und oft selbst dann –, ist die Arbeit mühsam und bringt wenig Belohnung. Woher wissen Sie überhaupt, dass es ein Update gibt? Sind die Änderungen für Sie nützlich, oder verhindern sie die Kompilierung beziehungsweise führen zu Bugs? Wie viele Konflikte verursacht Ihre Änderung mit anderen Änderungen, wenn Sie Funktionssignaturen in zahllosen Dateien aktualisieren?

Wenn die Arbeit mühsam ist und die Belohnung gering ausfällt, ist es ganz natürlich, sie aufzuschieben oder ganz zu ignorieren.

„Es wird zwei Sprints dauern, unseren React-Router zu aktualisieren.“

Irgendwann befinden Sie sich in einer Situation wie aus einem Kindermärchen: Sie haben den ganzen Sommer über Dependency-Updates aufgeschoben, nun ist der Winter da und Sie brauchen ein kleines Update für das Paket grasshopper-food. Doch dessen eigener Baum an Dependency-Updates steht in so vielen Konflikten mit Ihrer Codebasis, dass Sie nicht wissen, wie lange es dauern wird.

Bei Manifold zeigte sich das besonders deutlich beim Upgrade der Version des Routers, den wir mit React verwendeten. Wir hielten unsere Dependencies in der Frontend-Codebasis nicht aktuell. Als wir auf eine neue Hauptversion einer Router-Bibliothek umsteigen wollten, waren die geringfügigen Änderungen an den API-Signaturen mit zahllosen weiteren Konflikten und Inkompatibilitäten zwischen Dependencies verflochten. Andere Pakete waren seit unserem ersten Einsatz und dem ersten Versuch des Upgrades mehrfach aktualisiert worden. Die übrigen Dependencies von dem Router-Upgrade zu entwirren, war mühsam – insbesondere, wenn wir nach kleineren Zwischenversionen suchten, auf die wir upgraden konnten, um die Arbeit in überschaubare Schritte aufzuteilen.

Zunächst schien das Upgrade zu aufwendig, also schoben wir es erneut auf. Je länger wir warteten, ohne unsere Gewohnheiten zu ändern, desto größer wurde das Problem, weil immer mehr Dependencies aktualisiert wurden. Je länger wir warteten, desto mehr Zeit würde das Aktualisieren der Router-Version in Anspruch nehmen.

„Den Aufwand vorziehen.“

Dependency-Updates sind mühsam, eignen sich aber ideal dafür, den Aufwand „vorzuziehen“ – also Dependencies häufiger und gezielt zu aktualisieren. Dependencies aktualisieren sich im Laufe der Zeit. Je öfter Sie sie aktualisieren, desto kleiner ist jede einzelne Änderung und desto geringer der Gesamtaufwand (im Grunde ist das klassische Continuous Integration). Kleinere Änderungen machen das Problem klarer umrissen: Statt zu sagen „Dependency-Updates sind schwierig“, können Sie nun sagen: „Für das Update von Paket X auf Version Y sind Änderungen in diesen 12 Dateien nötig.“ Wenn Sie sich der verbleibenden mühsamen Arbeit stellen, werden Sie Wege finden, sie durch Automatisierung oder andere Maßnahmen zu verringern.

Wir aktivierten in allen unseren Repositories bei Manifold automatisierte Dependency-Updates. Mit den verfügbaren Tools (wie Snyk!) war die Einführung dieser Änderung nicht schwierig. Eine einzelne Person konnte sie eigenständig umsetzen und damit viel bewirken. Wir konnten die Änderung ohne „Erlaubnis einzuholen“ von den Repository-Verantwortlichen vornehmen, sondern stattdessen „um Verzeihung bitten“ – ein Konsens war nicht nötig, und es musste keine formelle Entscheidung getroffen werden. Die Person, die die Änderung umsetzte, wog den Aufwand (gering) gegen die mögliche positive Wirkung (groß) und die Schwierigkeit, sie rückgängig zu machen (gering – der Schalter musste nur umgelegt werden), ab.

Die Person nahm die Änderungen in einigen unserer Repositories vor und informierte die anderen Entwicklerinnen und Entwickler darüber, warum sie umgesetzt wurden und welchen Nutzen sie bringen würden. Außerdem bot sie an, allen beim Rückgängigmachen der Änderung zu helfen oder ihnen zu zeigen, wie sie das selbst tun konnten, falls sie keine Dependency-Updates wollten.

Nach und nach aktivierten weitere Entwicklerinnen und Entwickler automatisierte Dependency-Updates auch in den übrigen Repositories. Schon bald trafen täglich Updates ein.

„Wir verbringen zu viel Zeit damit, diese automatischen Dependency-Pull-Requests zu prüfen.“

Anfangs fühlt es sich seltsam an, wenn etwas, das früher selten vorkam und für Instabilität und Unsicherheit sorgte, nun täglich geschieht. Mit der Zeit gewöhnen sich alle daran und erkennen, dass das ursprüngliche Problem – Dependencies zu aktualisieren – keines mehr ist. An diesem Punkt ändert sich die Wahrnehmung, und der nächste besonders mühsame Teil des Aktualisierens von Dependencies wird zum neuen großen Problem.

Dependency-Updates täglich durchzuführen, verursacht selbst mit einem automatisierten Prozess zum Erstellen von Pull-Requests noch viel mühsame Arbeit. Das wurde bei Manifold zu unserem neuen großen Problem: Entwicklerinnen und Entwickler hatten das Gefühl, zu viel Zeit mit dem Prüfen und Genehmigen von Pull-Requests für Dependency-Updates zu verbringen. Daraus ergaben sich drei unterschiedliche Verhaltensweisen:

  • Die Pull-Requests ignorieren und in der Review-Warteschlange veralten lassen.

  • Lautstark über die Situation klagen – und entweder verlangen, die automatisierten Dependency-Updates abzuschalten, oder die Situation mit allen verfügbaren Mitteln zu beheben.

  • Die Pull-Requests weiterhin (oberflächlich) prüfen, genehmigen und mergen.

Die zweite Verhaltensweise – lautstark zu klagen – trieb die kontinuierliche Verbesserung unseres Prozesses für Dependency-Updates voran.

Wer gerade erst eine Prozessverbesserung umgesetzt hatte, hatte wahrscheinlich schon Ideen für den nächsten Schritt und war vermutlich auch mit dem aktuellen Zustand zufrieden. Andere Entwicklerinnen und Entwickler waren weniger unmittelbar in die Änderung eingebunden und konnten den akutesten Schmerzpunkt besser erkennen. Diejenigen, die das Problem ansprachen, statt es zu ignorieren oder hinzunehmen, hielten uns auf Kurs – auch wenn es wehtun kann, zu hören, dass die neue Prozessverbesserung nicht gut genug ist.

Im weiteren Verlauf bemühten wir uns, Änderungen und Verbesserungen schrittweise und leicht rückgängig zu machen und sie mit Bescheidenheit vorzuschlagen. Wenn die Stimmen derjenigen, die sich über die hohe Zahl an Dependency-Pull-Requests beklagten, laut genug wurden, erinnerte die Person, die die Verbesserung umgesetzt hatte, alle daran, dass sie die automatischen Updates abschalten konnten. Zugleich bat sie um etwas Zeit, um stattdessen für Entlastung zu sorgen. Die Verantwortlichen für die Codebasis hatten stets die Kontrolle, vertrauten aber darauf, dass Verbesserungen umgesetzt würden – sie hatten es schon erlebt!

Statt automatisierte Dependency-Updates zu deaktivieren oder seltener auszuführen, führten wir einen Mechanismus ein, der sie automatisch genehmigte und mergte, sofern die Tests bestanden wurden.

„Automatische Dependency-Änderungen verursachen einen Rückstau in unserer Continuous-Deployment-Pipeline.“

Nach genügend Verbesserungszyklen fühlt sich der Schmerz, den Sie erleben, ganz anders an als der, den Sie ursprünglich vorgezogen haben. Sie haben dann ein „gutes Problem“ – eines, das nur aufgrund Ihres Erfolgs besteht. Solche Probleme können mehr Aufwand für ihre Lösung erfordern, bringen aber oft größeren Bereichen einen Nutzen.

Nachdem wir Dependency-Update-Pull-Requests automatisch mergten, stießen die häufiger aktualisierten Services bei Manifold auf das nächste Problem. Wir hatten bereits eine GitOps-Pipeline, die Änderungen von Pull-Requests über Staging bis in die Produktion überführte. Dabei wurden Änderungen für Staging gebündelt und dieselbe Zusammenstellung anschließend in die Produktion übernommen. Da jede Bereitstellung in der Produktion von einer Service-Verantwortlichen oder einem Service-Verantwortlichen genehmigt werden musste und wir die in Staging übernommene Zusammenstellung danach nicht mehr änderten, blieben Änderungen der Entwicklerinnen und Entwickler hinter einem Rückstau automatisierter Dependency-Pull-Requests stecken.

Eigentlich betraf dieses Problem nicht nur automatische Dependency-Änderungen. Es war ein frühes Anzeichen dafür, dass ein hohes Änderungsvolumen unsere Deployment-Pipeline verstopfen würde. Das veranlasste die Entwicklerinnen und Entwickler, Build- und Testzeiten zu verbessern, und unser Platform-Team, die Zeit zu verkürzen, die der Deployer zum Erkennen und Anwenden von Änderungen benötigte.

Für Dependency-Pull-Requests implementierten wir in unserer Deployment-Pipeline eine Logik, die ihre Übernahme in die Produktion automatisch genehmigte sowie genehmigte Änderungen automatisch mergte und anwendete. Das hatte den willkommenen Nebeneffekt, dass Entwicklerinnen und Entwickler bei allen anderen Änderungen einen Schritt weniger erledigen mussten. Ein Prozess ohne menschliches Eingreifen für bestimmte Änderungsklassen erforderte großes Vertrauen in unsere Tests: Wir mussten sie entweder verbessern oder ausdrücklich anerkennen, dass sie mindestens so gute Ergebnisse lieferten wie ein Mensch.

Kontinuierliche Verbesserung funktioniert

Aus dem Wunsch, Dependency-Updates nicht zu großen Sprint-Aufgaben werden zu lassen, entstand noch viel mehr: schnellere Deployments, ein vollständig automatisierter Weg vom Pull-Request bis in die Produktion und eine klare Übereinkunft über die Aussagekraft unserer Tests.

Der Aufwand für Dependency-Updates geriet größtenteils in Vergessenheit und machte unserem größeren Problem mit der Deployment-Pipeline Platz. So gut diese Pipeline auch war, wir waren nicht zufrieden damit – und wären es auch nie gewesen. Genau so funktioniert kontinuierliche Verbesserung!

Gepostet in:

Weiterlesen

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSec: Sicherheit, die mit der KI-Entwicklung Schritt hält

KI-gestützte Entwicklung überholt herkömmliche Security-Workflows. Snyk Agentic AppSec setzt Security-Agenten ein, die Schwachstellen kontinuierlich finden, beheben und überprüfen – während Menschen die Maßstäbe festlegen.

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.