Eine selbstgehostete Keyright-Instanz sicher aktualisieren: Migrationen, null Ausfallzeit, Rollback
Ein Upgrade von selbstgehostetem Keyright ist unspektakulär, weil die App zustandslos ist und ihre eigenen Schema-Migrationen beim Start ausführt — kein SQL von Hand. Trotzdem wollen Sie ein sicheres Vorgehen: zuerst Postgres sichern, in einer Nicht-Produktivumgebung testen, das Image-Tag anpinnen und hinter Ihrem Load Balancer Knoten für Knoten aktualisieren. Hier ist die Abfolge, inklusive warum Rollback das Wiederherstellen der DB-Sicherung bedeutet und nicht das Herabstufen gegen eine bereits migrierte Datenbank.
Ein Stück Lizenzierungs-Infrastruktur zu aktualisieren klingt nach der Art Wartung, die man für 2 Uhr nachts ansetzt und bei der man die Luft anhält. Selbstgehostetes Keyright ist hier bewusst langweilig, und genau das ist der Punkt. Die App ist ein zustandsloser Container, der sein eigenes Datenbankschema besitzt und es beim Start migriert, sodass ein Upgrade meist auf „ein neueres Image holen und neu starten” hinausläuft. Aber „meist” ist nicht „immer”, und ein Lizenzserver sitzt auf dem Aktivierungspfad Ihrer Kunden, sodass Sie trotzdem ein Vorgehen wollen, das sicher ist und nicht nur schnell. Dieser Beitrag ist dieses Vorgehen — wie Upgrades tatsächlich funktionieren, die sichere Abfolge, wie man es ohne Ausfallzeit über mehrere Knoten macht, und die eine Sache am Rollback, über die alle stolpern.
Kernaussage: Upgrades sind unspektakulär, weil die App zustandslos ist und ihre EF-Core-Migrationen beim Start automatisch anwendet — kein SQL von Hand. Die sichere Abfolge bleibt: zuerst Postgres sichern, in einer Nicht-Produktivumgebung testen, das Image-Tag anpinnen und Knoten für Knoten aktualisieren. Rollback bedeutet die Datenbanksicherung wiederherstellen und das alte Tag erneut deployen — Migrationen laufen vorwärts, Sie stufen also nicht gegen eine bereits migrierte Datenbank herab.
Wie ein Upgrade tatsächlich funktioniert
Keyright hat genau zwei bewegliche Teile: eine zustandslose App und Ihre Datenbank. Diese Trennung macht Upgrades einfach.
Die App ist das Image ghcr.io/delta1-labs/keyright — ein ASP.NET-Core-Dienst auf Port 8080, ohne Datenbank darin. Jede Version der App trägt ihre eigenen Schema-Migrationen in das Image kompiliert. Beim Start verbindet sie sich mit dem PostgreSQL, auf das Sie sie mit KEYRIGHT_DB richten, und führt db.Database.Migrate() aus — der erste Start erstellt jede Tabelle und legt den Standard-Mandanten an, und ein Upgrade wendet nur die neuen Migrationen an. Sie führen niemals SQL von Hand aus und importieren niemals ein Schema. Eine neue Version, die eine Schemaänderung braucht, bringt diese Änderung mit und wendet sie in dem Moment an, in dem sie startet.
Weil die Migrationen beim Start laufen, muss die Datenbank erreichbar sein, wenn der neue Container startet. Der Readiness-Endpoint macht das sicher automatisierbar: GET /health/ready prüft die Datenbankverbindung und gibt 503 zurück, bis Postgres erreichbar und das Schema aktuell ist, während GET /health ein statisches Liveness-200 ist. Hängen Sie Ihren Load Balancer und Orchestrator an /health/ready und ein Knoten erhält schlicht keinen Datenverkehr, bis er migriert hat und wirklich bereit ist zu bedienen. Kann ein Knoten die Datenbank beim Start nicht erreichen, beendet er sich und startet neu, statt halb kaputt hochzukommen — ein Fehler, den Sie sofort sehen, nicht einer, der schlechte Antworten durchsickern lässt.
Die sichere Abfolge
Auch wenn die Mechanik ein einziger Schritt ist, erledigen Sie diese vier der Reihe nach.
1. Sichern Sie zuerst PostgreSQL. Das ist das ganze Sicherheitsnetz, denn der saubere Weg zurück aus einem schlechten Upgrade ist eine Datenbankwiederherstellung (dazu unten mehr). Nehmen Sie unmittelbar vor dem Upgrade ein pg_dump oder einen verwalteten Snapshot und bestätigen Sie, dass Sie Ihren KEYRIGHT_KEK und Ihren KEYRIGHT_SIGNING_KEY noch in ihrem separaten Secret-Store haben — die vollständige Disziplin steht in eine selbstgehostete Keyright-Instanz sichern und wiederherstellen. Ein Upgrade ohne frische Sicherung ist eine Einbahntür.
2. Testen Sie in einer Nicht-Produktivumgebung. Stellen Sie eine Kopie der Produktion in einer Staging-Datenbank wieder her, richten Sie eine Staging-Instanz mit dem neuen Image-Tag darauf und lassen Sie sie migrieren. Hier wird eine Schema-Migration entweder sauber gegen echt geformte Daten angewendet oder sagt Ihnen etwas, das Sie wissen wollen, bevor es die Produktion berührt. Führen Sie einen Smoke-Test der Staging-Instanz durch (unten) und befördern Sie das Tag erst dann.
3. Rollen Sie es aus. Holen Sie das angepinnte neue Tag und starten Sie neu. Unter Docker Compose:
docker compose pull
docker compose up -d
Compose erstellt den Container aus dem neuen Image neu; er migriert beim Start und kommt mit demselben KEYRIGHT_DB, KEYRIGHT_KEK und derselben Selbstlizenz zurück. Unter Kubernetes mit dem Helm-Chart heben Sie das Image-Tag an:
helm upgrade keyright delta1/keyright --reuse-values \
--set image.tag=2.2.3
4. Verifizieren. Bestätigen Sie, dass die neue Version läuft und weiterhin lizenziert ist (siehe den Abschnitt zur Verifizierung unten).
Der Betriebsleitfaden führt dieselbe Abfolge als knappe Checkliste, die Sie in ein Runbook einfügen können.
Rollierendes Upgrade ohne Ausfallzeit
Wenn Sie bereits zwei oder drei Knoten für Hochverfügbarkeit betreiben, hört ein Upgrade auf, ein Ausfall zu sein. Die App ist zustandslos und innerhalb einer Minor-Linie abwärtskompatibel, sodass Sie ein Replikat nach dem anderen aktualisieren können, während der Load Balancer weiter aus den anderen bedient.
Das Muster ist: Nehmen Sie einen Knoten aus dem Pool, bringen Sie ihn auf dem neuen Tag zurück, warten Sie, bis GET /health/ready besteht, und gehen Sie dann zum nächsten über. Weil es keine Session-Affinität gibt — jeder Knoten kann jede Anfrage beantworten — bleibt nichts hängen, wenn ein Knoten den Pool verlässt, und die Nebenläufigkeit bleibt sicher, weil Sitze und Credits ein serialisierbares Check-and-Deduct in Postgres nutzen, egal welcher Knoten die Anfrage bearbeitet.
Unter Kubernetes ist das einfach ein RollingUpdate mit der Readiness-Probe, die auf /health/ready zeigt:
# values.yaml
replicaCount: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # ein Replikat nach dem anderen
maxSurge: 1
helm upgrade keyright delta1/keyright -f values.yaml --set image.tag=2.2.3
Kubernetes ersetzt einen Pod nach dem anderen, jeder neue Pod führt seine Migrationen aus und tritt dem Service erst bei, sobald /health/ready grün ist, und die alten Pods bedienen weiter, bis ihr Ersatz bereit ist. Sie können es mit kubectl rollout status deploy/keyright verfolgen und das Ausrollen des Deployments mittendrin anhalten oder rückgängig machen, falls ein neuer Pod nie bereit wird — beachten Sie jedoch, dass das Rückgängigmachen des Ausrollens die Pods zurücksetzt, nicht die Datenbank, was für die Rollback-Geschichte unten wichtig ist.
Ein Einzelknoten-Deployment bekommt das nicht umsonst: docker compose up -d hat ein kurzes Neustartfenster, während der eine Container neu erstellt wird und migriert. Für einen Lizenzserver ist das meist in Ordnung, weil Clients ein signiertes Lease zwischenspeichern und zwischen Aktivierungen offline validieren, sodass ein kurzer Neustart von ihrer Kulanz abgefedert wird — aber wenn Sie wirklich wartung ohne Ausfallzeit wollen, ist das das Argument für zwei Knoten.
Pinnen Sie Ihre Image-Tags an
Verwenden Sie kein schwebendes latest. Pinnen Sie eine konkrete Version an — image.tag=2.2.3 unter Helm, ein expliziter Tag in Ihrer Compose-Datei — damit das, was Sie in Staging getestet haben, Byte für Byte das ist, was in die Produktion ausgerollt wird, und damit ein um 3 Uhr nachts umgeplanter Pod nicht klammheimlich ein neueres Image holt, das Sie nie validiert haben. Reproduzierbare Deployments sind der Unterschied zwischen einem Upgrade, das Sie durchgeführt haben, und einem, das Ihnen passiert ist. Die Konfigurationsreferenz behandelt das Anpinnen von Tags neben den übrigen Helm-Werten; dieselbe Pin-Disziplin macht die Air-Gapped-Image-Transfers reproduzierbar, wenn Sie ein Tarball über die Luftschranke tragen.
Rollback: Sicherung wiederherstellen, nicht herabstufen
Hier ist der Teil, den Sie verinnerlichen sollten. Migrationen laufen vorwärts. Eine neue Version kann das Schema beim Start geändert haben, und das alte Image weiß nicht, wie es gegen dieses neuere Schema laufen soll. Sie machen also kein Rollback, indem Sie das alte Image-Tag auf eine bereits migrierte Datenbank richten — das ist der eine Zug, der eine wiederherstellbare Lage in ein Chaos verwandelt.
Das unterstützte Rollback ist:
- Stellen Sie die PostgreSQL-Sicherung wieder her, die Sie in Schritt eins genommen haben, und bringen Sie Schema und Daten in ihren exakten Zustand vor dem Upgrade zurück.
- Deployen Sie das vorherige Image-Tag erneut gegen die wiederhergestellte Datenbank.
Weil das alte Image und die wiederhergestellte Datenbank (altes Schema) zusammenpassen, kommt die Instanz direkt wieder hoch. Genau deshalb beginnt die sichere Abfolge mit einer Sicherung und testet in einer Nicht-Produktivumgebung: Sie wollen ein schlechtes Upgrade in Staging erwischen, und falls eines doch die Produktion erreicht, wollen Sie einen sauberen Wiederherstellungspunkt statt eines Schemas, das Sie von Hand bearbeiten müssen. (Unter Kubernetes setzen helm rollback oder kubectl rollout undo die Pods auf das alte Tag zurück — nützlich, wenn das neue Image schlicht nicht startet —, aber sie setzen nicht die Datenbank zurück, kombinieren Sie das also mit einer DB-Wiederherstellung, falls eine Migration bereits gelaufen ist.)
Nach dem Upgrade verifizieren
Drei Prüfungen bestätigen ein gutes Upgrade:
GET /healthmeldet die neueversionund gibt200zurück — das neue Image läuft und ist migriert.GET /admin/self-licensemeldet weiterhin Ihre Edition und eine aktive Phase — Keyright ist fail-closed, also muss nach dem Upgrade weiterhin eine gültigeKEYRIGHT_SELF_LICENSEvorhanden sein, sonst gibt jede Kundenanfrage402zurück. Der Schlüssel ändert sich über ein Upgrade hinweg nicht; diese Prüfung bestätigt nur, dass er übernommen wurde und die Instanz bedient, nicht gesperrt ist. (/admin/self-diagnosticsliefert dasselbe in einem Aufruf: Version, ausstehende Migrationen und Lizenzphase.)- Ein Smoke-Test
GET /v1/validategegen eine bekannte Lizenz gibt ein gültiges Lease zurück — der Beweis, dass der Signaturschlüssel weiterhin Leases erzeugt, die Ihre Clients von Ende zu Ende akzeptieren.
Bestehen alle drei, ist das Upgrade erledigt und Sie sind weiterhin auf dem Aktivierungspfad.
Wo das hineinpasst
Upgrades sind das vierte Stück des produktiven Betriebs von selbstgehostetem Keyright, neben Hochverfügbarkeit, Sicherung und Wiederherstellung und Air-Gapped-Betrieb. Weil Keyright in beiden Bereitstellungsmodi ein einziges Produkt ist, ist die Form „zustandslose App plus Ihre Datenbank” — und damit diese ganze Upgrade-Geschichte — identisch, egal ob Sie einen Knoten auf einem Laptop oder drei hinter einem Load Balancer betreiben. Selbstgehostetes Keyright wird pro Deployment über Standard, Pro und Enterprise bepreist (Preise); rollierende Upgrades ohne Ausfallzeit verlangen die zwei oder drei Knoten, die Pro und Enterprise abdecken, aber die Migrate-beim-Start-Mechanik und die Sicherung-zuerst-Disziplin sind auf jeder Stufe gleich.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.