Anleitungen

App aus dem Repo konfigurieren

Legen Sie einen Ordner deploy/ in Ihr Repo, und jeder Push wendet ihn an, mit einer Postgres-Datenbank und einem Bucket direkt neben der App deklariert. Entfernen Sie die Datei, bleiben die Daten erhalten.

Derzeit auf ausgewählten dedizierten Instanzen verfügbar. Für die gemeinsame Plattform als Nächstes geplant.

Die Konfiguration Ihrer App kann im Repo liegen, direkt neben dem Code. Legen Sie die Manifeste in einen Ordner deploy/ im Wurzelverzeichnis des Repositorys, dann wendet jeder Push auf den Branch, den die App überwacht, sie an. Replicas, Umgebung, eine Postgres-Datenbank und ein Bucket im Objektspeicher werden so mit dem Code versioniert und wie Code im Review geprüft, und die Console zeigt, aus welchem Commit die laufende Konfiguration stammt.

Ordner deploy/ anlegen

nyxory liest die Dateien *.yaml und *.yml auf der obersten Ebene von deploy/. Ohne den Ordner ändert sich nichts, und die Plattform erzeugt die Konfiguration der App weiter wie bisher. Andere Ordner wie k8s/, manifests/ oder Charts werden beim Push nicht gelesen. Verschieben Sie die Manifeste, die Sie selbst verantworten wollen, deshalb nach deploy/.

deploy/ wird immer im Wurzelverzeichnis des Repositorys gelesen, auch wenn eine App aus einem Unterordner baut. In einem Monorepo liest deshalb jede App denselben Ordner. Innerhalb eines Projekts wird der Push einer zweiten App aus diesem Repository abgelehnt, weil die Objekte, die er nennt, schon der ersten App gehören.

Objekttyp Was die Plattform tut
Deployment wird angewendet
Service wird angewendet, nur ClusterIP und mit Selector
ConfigMap wird angewendet
Ingress bildet jede Regel auf die eigene Domain der App ab, damit TLS und der Schutz der öffentlichen Adresse bei der Plattform bleiben
Postgres stellt eine Datenbank für das Projekt bereit und behält sie
Bucket stellt einen Bucket für das Projekt bereit und behält ihn, auf Instanzen mit Objektspeicher

Postgres und Bucket verwenden apiVersion: nyxory.com/v1alpha1, die anderen ihre üblichen apps/v1, v1 und networking.k8s.io/v1.

Der App-Container läuft mit dem Image, das nyxory aus dem gepushten Commit gebaut hat. Schreiben Sie image: IMAGE_REF dort, wo dieses Image hingehört. Andere Images, etwa ein Sidecar redis:7, bleiben, wie sie dastehen. Fehlt IMAGE_REF, bekommt der erste Container des ersten Deployments das gebaute Image. Namespace, Plattform-Labels und Eigentümerschaft setzt die Plattform, lassen Sie diese Angaben also weg.

# deploy/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels: {app: web}
  template:
    metadata:
      labels: {app: web}
    spec:
      containers:
        - name: web
          image: IMAGE_REF
          ports: [{containerPort: 8080}]
          env:
            - {name: GREETING, value: "hello"}
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector: {app: web}
  ports: [{name: http, port: 80, targetPort: 8080}]

Behalten Sie die Namen, die der erste Deploy angelegt hat, meist den Namen der App. Ein neuer Name legt ein zweites Deployment neben dem ersten an.

Mit der erzeugten Konfiguration beginnen

Die App-Seite in der Console zeigt unter Configuration die Manifeste, mit denen die App gerade läuft, aus dem Cluster gelesen, jeweils ein Objekt. Copy legt das gewählte Objekt in die Zwischenablage, und Sie fügen es als Ausgangspunkt in eine Datei in deploy/ ein. Laufzeitfelder sind bereits entfernt, und die Namen sind die, die der erste Deploy angelegt hat. Setzen Sie vor dem Push image: IMAGE_REF, entfernen Sie metadata.namespace und jedes Label und jede Annotation unter nyxory.com, und lassen Sie Objekttypen weg, die die Tabelle oben nicht nennt. Werte mit schlüsselartigen Namen erscheinen maskiert, und Secrets werden nicht angezeigt. Speichern Sie diese Werte mit nyx_set_secret und verweisen Sie über den Namen auf das Secret.

Was bei einem Push passiert

Pushen Sie auf den überwachten Branch, baut nyxory das Image aus dem neuen Commit, wie bei jedem Deploy per Push. Dann prüft nyxory jedes Objekt in deploy/ und führt für alle einen Probelauf (Dry Run) aus. Erst wenn alles besteht, wendet es den Ordner an und rollt das neue Image aus. Die App-Seite in der Console zeigt das Ergebnis als „Source: from your repo, deploy/ @ <commit>“ unter Configuration an.

Entfernt wird nichts. Ein Objekt, das die erzeugte Konfiguration hatte und deploy/ nicht nennt, bleibt bestehen, ebenso jedes Feld, das deploy/ nie setzt.

Ein abgelehnter Push lässt die App weiterlaufen

Verstößt ein Objekt gegen eine Regel, lehnt nyxory den ganzen Push ab, bevor irgendetwas geschrieben wird, und die laufende Version bleibt in Betrieb. Die App-Seite meldet das unter Configuration, mit Datei und Grund:

Last push rejected, nothing applied,
the running version stays:
deploy/postgres.yaml: Postgres orders, tier dedicated
is not available yet; use tier: shared

Korrigieren Sie die Datei und pushen Sie erneut. Diese Fälle werden immer abgelehnt:

  • ein Secret, denn Secret-Werte kommen nie aus dem Repository (Ihr Coding-Agent speichert sie mit nyx_set_secret, und deploy/ verweist über den Namen auf sie)
  • jeder Objekttyp, der nicht in der Tabelle oben steht, RBAC und clusterweite Objekte eingeschlossen
  • ein metadata.namespace, der nicht der des Projekts ist, sowie Labels oder Annotationen unter nyxory.com
  • traefik.* und plattformeigene Annotationen an einem Ingress sowie ein Ingress-Host, der keine Adresse der App ist (fügen Sie die Domain zuerst hinzu)
  • Host-Namespaces, hostPath-Volumes, privilegierte Container und ein serviceAccountName, der nicht der eigene der App ist
  • ein Service vom Typ LoadBalancer oder NodePort, externalIPs und Namen, die der Plattform vorbehalten sind (nyx-*, nyxory-*)
  • ein Ordner deploy/ oder eine Datei darin als symbolischer Link

Der Ordner fasst etwa 10 KB YAML. Ist er größer, schlägt der Build mit einer entsprechenden Meldung fehl.

Ihr Coding-Agent

Ist der nyxory-MCP verbunden, ruft Ihr Coding-Agent nyx_config_reference auf, bevor er deploy/ schreibt. Die Antwort nennt die erlaubten Objekttypen, die Felder von Postgres und Bucket mit Typ, Standardwert und Beschreibung, so wie sie auf Ihrer Instanz installiert sind, die Regeln, gegen die ein Push geprüft wird, und ein Beispiel pro Objekttyp. Die gesamte Dokumentation gibt es außerdem als eine Datei unter nyxory.com/llms-full.txt.

Die Plattform committet nicht in Ihr Repository. Der Agent der Plattform arbeitet auf einem schreibgeschützten Klon. Braucht eine Korrektur eine Änderung an deploy/ oder an anderen Dateien, die Sie geschrieben haben, zeigt er die Änderung, und Sie oder Ihr Coding-Agent committen und pushen sie.

Postgres-Datenbank in deploy/postgres.yaml deklarieren

Deklarieren Sie die Datenbank neben der App, und nyxory legt sie auf dem Datenbankserver der Plattform an, mit zwei Rollen und zwei Secrets in Ihrem Projekt. Niemand schreibt irgendwo ein Passwort hin.

# deploy/postgres.yaml
apiVersion: nyxory.com/v1alpha1
kind: Postgres
metadata:
  name: orders
spec:
  tier: shared
Feld Werte
tier Pflicht, und shared ist der einzige Wert aus deploy/
secretName optional, Standard <name>-postgres, nach dem ersten Push fest
auth password (Standard) oder certificate, siehe unten
deletionPolicy wird ignoriert, Daten aus dem Repo bleiben immer erhalten

Jedes andere Feld und jedes Label oder jede Annotation unter nyxory.com führt zur Ablehnung des Pushs.

Die zwei Secrets. orders-postgres ist für die laufende App. Die zugehörige Rolle liest und schreibt Zeilen und kann das Schema nicht ändern. orders-postgres-migrate ist für die Migration, die zugehörige Rolle besitzt das Schema. Beide enthalten dieselben Schlüssel:

Schlüssel Enthält
DATABASE_URL den vollständigen Connection-String, mit sslmode=verify-full
PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD dieselben Werte einzeln, für Tools, die keine URL lesen
PGSSLMODE, PGSSLROOTCERT verify-full und /etc/nyxory-postgres/ca.crt
ca.crt das CA-Zertifikat des Datenbankservers
PGHOST_RO den Host eines Read-only-Replikats, nur wenn der Server mehr als eine Instanz betreibt

Das Deployment liest das Secret mit envFrom und hängt es als Volume ein, damit der Client die CA findet, gegen die der Connection-String prüft:

    spec:
      containers:
        - name: web
          image: IMAGE_REF
          envFrom:
            - secretRef: {name: orders-postgres}
          volumeMounts:
            - name: pgca
              mountPath: /etc/nyxory-postgres
              readOnly: true
      volumes:
        - name: pgca
          secret: {secretName: orders-postgres}

Das Secret erscheint wenige Sekunden nach dem Push. Ein Pod, der früher startet, wartet darauf und startet von selbst, sobald es da ist.

Migrationen bleiben ein Job. Jobs sind in deploy/ nicht erlaubt. Ihr Coding-Agent führt die Migration deshalb als eigenen Job über nyx_deploy_manifests aus, mit dem Secret -migrate. Ein Framework, das beim Start migriert, scheitert mit der App-Rolle. Genau so soll die Trennung wirken. Verlagern Sie diesen Schritt in den Job.

Eine Datenbank teilen. Eine App deklariert sie, andere lesen sie. Eine zweite App, die eine Datenbank mit demselben Namen deklariert, wird abgelehnt. Sie liest stattdessen das Secret der ersten App mit envFrom, und ihre Seite zeigt die Datenbank als „used, declared by <app>“.

Bucket in deploy/bucket.yaml deklarieren

Auf Instanzen mit Objektspeicher ist ein Bucket nur eine weitere Datei. Anderswo wird der Push abgelehnt, bevor irgendetwas geschrieben wird, mit der Meldung „object storage is not set up on this instance“.

Buckets gibt es auf Instanzen bei AWS und STACKIT. Bei AWS wird jeder Bucket mit Verschlüsselung im Ruhezustand (SSE-S3) und mit gesperrtem öffentlichem Zugriff angelegt.

# deploy/bucket.yaml
apiVersion: nyxory.com/v1alpha1
kind: Bucket
metadata:
  name: reports
spec:
  versioning: false
  credentials: auto
Feld Werte
versioning true oder false (Standard), true behält überschriebene Objekte
credentials auto (Standard), keys oder identity
secretName optional, Standard <name>-s3, nach dem ersten Push fest
deletionPolicy wird ignoriert, Daten aus dem Repo bleiben immer erhalten

auto wählt den stärksten Zugriff, den die Instanz bietet. Bei AWS ist das identity. Dabei erreicht die App den Bucket über die Identität ihres Pods, und nirgends existiert ein Schlüssel. Bei anderen Anbietern ist es keys, ein Zugriffsschlüsselpaar im Secret. Ein Modus, den die Instanz nicht anbietet, versetzt den Bucket in einen Fehlerzustand mit dem Grund CredentialsUnavailable und fällt nie stillschweigend auf etwas anderes zurück.

Für den Zugriff per Identität läuft der Pod unter dem eigenen ServiceAccount der App, der den Namen der App trägt:

    spec:
      serviceAccountName: web
      containers:
        - name: web
          image: IMAGE_REF
          envFrom:
            - secretRef: {name: reports-s3}

Das Secret reports-s3 enthält in beiden Modi dieselben Namen, der App-Code ändert sich beim Wechsel also nicht:

Schlüssel Enthält Modus
S3_BUCKET, S3_REGION, S3_ENDPOINT Bucket-Name, Region, Endpoint beide
S3_FORCE_PATH_STYLE false bei AWS, true, wo der Endpoint Path-Style nutzt beide
AWS_REGION, AWS_ENDPOINT_URL_S3 Region und Endpoint unter den Namen, die AWS-SDKs von selbst lesen beide
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY das Zugriffsschlüsselpaar nur keys

Bauen Sie den S3-Client mit der Standard-Credential-Chain des SDK und lesen Sie nur den Bucket-Namen aus. Die Chain findet im Modus keys das Schlüsselpaar und im Modus identity die Identität des Pods.

import os, boto3

# region, endpoint, credentials: the default chain
s3 = boto3.client("s3")
s3.put_object(Bucket=os.environ["S3_BUCKET"],
              Key="hello.txt", Body=b"hi")
import {
  S3Client, PutObjectCommand,
} from "@aws-sdk/client-s3";

const env = process.env;
const s3 = new S3Client({
  forcePathStyle: env.S3_FORCE_PATH_STYLE === "true",
});
await s3.send(new PutObjectCommand({
  Bucket: env.S3_BUCKET,
  Key: "hello.txt",
  Body: "hi",
}));

Im Modus identity startet die Plattform Pods, die beim Einrichten des Zugriffs schon liefen, einmal neu, und der Bucket zeigt so lange „waiting for access“. Die Grenze ist das Projekt. Jede App im Projekt erreicht die Buckets des Projekts, keine App in einem anderen Projekt. Bucket-Namen sind bei AWS global. Ein anderswo vergebener Name endet deshalb mit dem Grund BucketNameTaken, und ein anderer metadata.name behebt das.

Daten aus dem Repo bleiben erhalten

Datenbanken und Buckets, die in deploy/ deklariert sind, gehören zum Projekt, nicht zur App:

  • Entfernen Sie die Datei, bleiben die Daten erhalten. Die App-Seite zeigt „No longer in deploy/, last declared in <commit>. The data stays.“
  • Auch wenn Sie die App löschen, bleiben sie erhalten. Andere Apps, die ihr Secret lesen, arbeiten weiter.
  • deletionPolicy: Delete in der Datei wird ignoriert, und die App-Seite weist darauf hin.
  • Prüfungen laufen, bevor irgendetwas geschrieben wird. nyxory prüft das Limit pro Projekt für Datenbanken und Buckets, ob der Name des Secrets frei ist und ob tier und secretName einer bestehenden Datenbank unverändert sind.

Löschen ist ein eigener, bewusster Schritt für Workspace-Inhaber und Admins. Auf der App-Seite verlangen Delete database und Delete bucket, dass Sie zur Bestätigung den Namen eintippen. Alternativ erledigt Ihr Coding-Agent dasselbe nach Ihrer Freigabe mit nyx_data (type: postgres oder type: bucket). Ein Bucket wird geleert und entfernt. Eine Datenbank wird nach einem letzten Dump gelöscht. Auf einer Instanz ohne Dump-Ziel bleibt sie bestehen, bis eines eingerichtet ist, und der Dialog sagt Ihnen das. Nehmen Sie die Datei zuerst aus deploy/, sonst legt der nächste Push das Objekt wieder an, leer.

Hat Ihr Coding-Agent für diese App schon früher eine Datenbank oder einen Bucket angelegt, übernimmt deploy/ das Objekt, wenn es dort mit demselben Namen, demselben Secret und bei einer Datenbank mit demselben tier deklariert ist. Ab dann verwaltet deploy/ es, und Änderungen laufen über das Repo.

Postgres mit Client-Zertifikat nutzen

Optional erfolgt die Anmeldung an einer Datenbank per Client-Zertifikat statt per Passwort:

spec:
  tier: shared
  auth: certificate

Dann hat keine der beiden Rollen mehr ein Passwort. Jeder Pod der deklarierenden App bekommt ein Client-Zertifikat für seine Rolle, als Dateien unter /etc/nyxory-postgres-cert eingehängt und nie in einem Secret gespeichert. Das Zertifikat gilt 72 Stunden, die Plattform ersetzt es nach 48. Auf einer Instanz ohne Zertifikatsanmeldung meldet die Datenbank den Grund CertificateAuthDisabled, und nichts ändert sich.

Die Secrets ändern ihren Aufbau. PGPASSWORD fällt weg, DATABASE_URL enthält sslcert und sslkey, und PGSSLCERT und PGSSLKEY zeigen auf die beiden Dateien. Ein Deployment in deploy/, das das Secret mit envFrom liest, ergänzt die Plattform automatisch um das Zertifikats-Volume, den CA-Mount und den ServiceAccount der App. Die Zeile envFrom bleibt also dieselbe wie mit Passwort. Die Plattform ergänzt diese Teile bei einem Push, sobald die Datenbank existiert. Kommen Datenbank und Deployment im selben ersten Push, pushen Sie noch einmal, wenn die Datenbank bereit ist.

Eine offene Verbindung funktioniert weiter, wenn das Zertifikat rotiert. Jede neue Verbindung muss die aktuelle Datei vorlegen, und Treiber lesen sie unterschiedlich:

Treiber Liest das neue Zertifikat Was zu tun ist
libpq: psql, psycopg 2 und 3, Django, SQLAlchemy auf psycopg, Rails pg bei jedem Verbindungsaufbau nichts, DATABASE_URL oder PGSSLCERT/PGSSLKEY so lassen, wie sie sind
node-pg (pg) bei jedem neuen Client, der aus connectionString erzeugt wird connectionString: process.env.DATABASE_URL übergeben und die Dateien nicht einmalig beim Start in ein ssl-Objekt einlesen
pgx (Go) nie, ParseConfig lädt die Dateien einmal TLSConfig.Certificates leeren und GetClientCertificate so setzen, dass es bei jedem Aufruf PGSSLCERT und PGSSLKEY mit tls.LoadX509KeyPair lädt, sonst scheitert der Pool, sobald das erste Zertifikat abläuft
pgjdbc (Java, Spring, Hikari) bei jeder neuen physischen Verbindung sslrootcert, sslcert und sslkey aus der Umgebung in der JDBC-URL übergeben
asyncpg, Prisma, npgsql nicht geprüft bei auth: password bleiben

Im Zertifikatsmodus wird eine Datenbank nicht geteilt. Nur die App, die sie deklariert, erhält das Zertifikat für die App-Rolle. Der Migrations-Job läuft unter einem ServiceAccount, der wie das Migrations-Secret heißt, orders-postgres-migrate, und den die Plattform dafür anlegt. Weil der Job über Ihren Coding-Agenten läuft und nicht über deploy/, trägt er diesen serviceAccountName und das Zertifikats-Volume selbst. Der Wechsel zwischen password und certificate ist eine Änderung in der Datei, und die Rollen behalten ihre Daten und Rechte. Das Zertifikats-Volume folgt dem Modus, den die Datenbank hatte, als der Push angewendet wurde. Pushen Sie deshalb noch einmal, nachdem die Datenbank den neuen Modus meldet, in beide Richtungen.

Warum ein Zertifikat und kein Token. Postgres prüft Zugangsdaten nur beim Öffnen einer Verbindung, und die meisten Treiber und Pools lesen das Passwort einmal beim Start. Ein Token, das sich stündlich ändert, scheitert deshalb beim nächsten Neuverbinden. Ein Zertifikat wird beim Öffnen einer Verbindung von der Platte gelesen, also genau dann, wenn Postgres es prüft, und Postgres verifiziert es nativ.

Noch nicht in deploy/ möglich

  • Einstellungen, die nur ein Anbieter kennt, etwa ein eigener Verschlüsselungsschlüssel oder Aufbewahrungsfristen für einen Bucket
  • dieselbe Änderung auf mehrere Instanzen gleichzeitig ausrollen
  • mehrere Apps in einem Repository, jede mit eigenem deploy/

Sagen Sie uns, was davon Sie brauchen.

Siehe auch: Bei jedem Push ausrollen · Speicher und Backups · Aus einem privaten Repo ausrollen

Fragen Sie eine KI nach nyxory