Bring-your-own-App (BYOA)
Bring-your-own-App (BYOA)
Der Polycrate-Block ayedo/k8s/byoa paketiert eine 15-Factor-App für Kubernetes über block.config und Jinja — ohne Helm, ohne Image-Build, ohne eingebettete Datenbank.
Produktseite: Bring-your-own-App. Releases: Polycrate Hub. Methodik: 15-Factor Apps.
BYOA ist die Release- und Run-Schicht für ein bereits gebautes Container-Image. Die Codebase bleibt im VCS der Anwendung. Backing Services bleiben Geschwister-Blöcke (CloudNativePG, Valkey, RustFS). Metriken laufen über VictoriaMetrics, Logs über VictoriaLogs — nicht über Prometheus oder Loki.
Wann BYOA, wann ohMyHelm?
| BYOA | ohMyHelm | |
|---|---|---|
| Paketierung | Polycrate-Block, Jinja, block.config |
Helm Chart, values.yaml |
| Ausrollen | polycrate run <instanz> install |
helm upgrade --install / Argo CD |
| Zielgruppe | Workspaces, die Apps wie Infrastruktur als Block betreiben | Teams mit bestehendem Helm/GitOps-Pfad |
| Image-Build | außerhalb (CI) | außerhalb (CI) |
| Backing Services | Geschwister-Blöcke, per Env angehängt | als URL in values.yaml |
Beide Wege implementieren denselben 15-Factor-Vertrag. Ein Image, das in BYOA läuft, kann derselbe Build sein, den ohMyHelm deployt.
Schnellstart
Block-Instanz in der workspace.poly:
blocks:
- name: acme-shop
from: cargo.ayedo.cloud/ayedo/k8s/byoa
config:
namespace: acme-shop
image:
repository: registry.acme.corp/acme/shop
tag: "1.2.3"
env:
- name: LOG_LEVEL
value: info
env_from:
- secretRef:
name: acme-shop-db-app
workloads:
web:
kind: Deployment
replicas: 2
ports:
- name: http
containerPort: 8080
probes:
liveness:
httpGet:
path: /healthz
port: http
readiness:
httpGet:
path: /readyz
port: http
worker:
kind: Deployment
replicas: 3
command: [./worker]
migrate:
kind: Job
hook: pre
command: [./migrate]
polycrate run acme-shop render
polycrate run acme-shop install
polycrate run acme-shop status
namespace ist Pflicht. Entweder image.repository auf Block-Ebene oder pro Workload. Ohne image.tag oder image.digest lehnt install implizites :latest ab.
Kopierbare Instanzen liegen in der examples.poly des Blocks (polycrate block inspect bzw. Hub).
Actions
| Action | Verhalten |
|---|---|
render |
Manifeste nach Block-Artefakten schreiben, kein Cluster-Write |
install |
Validieren, dann Namespace → RBAC → NetworkPolicies → ConfigMaps/Secrets → Extra-Manifeste → Pre-Jobs löschen und neu anlegen → auf Complete warten → Workloads → Services → HPA/PDB → Ingress oder Gateway → CronJobs → VMPodScrapes → Post-Jobs (überspringen, wenn schon Complete) |
status |
Ressourcen mit app.kubernetes.io/instance={{ block.name }} |
uninstall |
Dieselben Ressourcen löschen. PVCs bleiben, außer persistence.retain ist false. Namespace bleibt, außer delete_namespace: true |
Server-Side Apply mit field_manager: polycrate und force_conflicts: true.
Maps statt Listen
In workspace.poly mergen Maps sauber (workloads.web.replicas). Listen ersetzen sich.
Maps: workloads, configmaps, secrets, network_policies, rbac.roles, rbac.role_bindings, rbac.cluster_roles, rbac.cluster_role_bindings, init_containers, sidecars.
Listen: env, ports, volume_mounts, volumes, tolerations, image_pull_secrets, extra_manifests.
Naming
- Primärer Service-Workload (erster mit
ports, sonst erstes Deployment) ={block.name} - Alle anderen =
{block.name}-{key} - Override:
workloads.<key>.name
Labels: app.kubernetes.io/{name,instance,component,part-of,managed-by} plus block.labels. Uninstall selektiert app.kubernetes.io/instance.
Workloads
kind |
Objekt | 15-Factor-Prozesstyp |
|---|---|---|
Deployment (Default) |
Deployment | web, worker (Faktor 6 + 8) |
StatefulSet |
StatefulSet, optional PVC-Templates | nur wenn stabile Identität nötig ist |
DaemonSet |
DaemonSet | node-lokal, nicht die Standard-App |
Job |
Job | Admin / Migration (Faktor 12) |
CronJob |
CronJob | geplanter Admin (Faktor 8 + 12) |
Pro Workload unter anderem: image (Override), command / args, ports, env / env_from (hängen an die Top-Level-Listen an), probes, resources, security_context, volume_mounts, volumes, init_containers, sidecars, hpa, pdb, ingress, gateway, service, node_selector, tolerations, affinity, termination_grace_period_seconds (Default 30).
Pre-Jobs (Faktor 12)
migrate:
kind: Job
hook: pre # pre | post | none
command: [./migrate]
hook: pre läuft bei jedem install: bestehende Pre-Jobs werden foreground gelöscht, neu angelegt, auf Complete gewartet, danach erst Workloads. Migrationen müssen idempotent sein (CREATE TABLE IF NOT EXISTS, nicht „einmalig INSERT ohne Guard“).
hook: post läuft nach den Workloads und wird übersprungen, wenn der Job bereits Complete ist.
CronJobs
nightly:
kind: CronJob
schedule: "0 3 * * *"
concurrency_policy: Forbid
command: [./report]
Konfiguration und Secrets (Faktor 3)
configmaps:
app:
data:
FEATURE_CHECKOUT: "true"
secrets:
app:
string_data:
SESSION_KEY: "{{ workspace.secrets.acme_shop.session_key }}"
env_from:
- configMapRef:
name: acme-shop-app
- secretRef:
name: acme-shop-app
Echte Credentials gehören nach secrets.poly, nicht in die workspace.poly.
Deployment-, StatefulSet- und DaemonSet-Pods bekommen Annotations (nicht Labels — SHA256 ist 64 Zeichen):
checksum/configmap-<key>undchecksum/configmapschecksum/secret-<key>undchecksum/secrets
Ändert sich die Payload, rollt der nächste install die Pods.
Exposure (Faktor 7 + 15)
Ingress und Gateway sind default aus und gegenseitig exklusiv. Beide gleichzeitig aktiv → install bricht ab.
Ingress
workloads:
web:
ports:
- name: http
containerPort: 8080
ingress:
enabled: true
class: nginx
host: shop.acme.example
path: /
tls:
enabled: true
issuer: letsencrypt-production
Gateway API (Envoy)
workloads:
web:
ports:
- name: http
containerPort: 8080
gateway:
enabled: true
create_gateway: true
gateway_class_name: eg-shared
host: shop.acme.example
tls:
issuer: letsencrypt-production
Erzeugt Certificate, Gateway, HTTPRoute sowie Envoy Client-/BackendTrafficPolicy. Anbindung an ein vorhandenes Shared-Gateway: create_gateway: false plus parent_gateway_name / parent_gateway_namespace / parent_gateway_section_name.
TLS terminiert am Edge. Die App bindet weiter einen unprivilegierten Container-Port.
Integration in die Polycrate API
Der Polycrate Operator beobachtet Ingress immer und HTTPRoute, sobald die Gateway-API-CRDs im Cluster liegen (CLI Spec 300). Der BYOA-Block schreibt denselben Kundenvertrag auf beide Ressourcen:
| Annotation | Default | Wirkung |
|---|---|---|
polycrate_endpoint_monitor |
"true" |
Operator legt pro Host eine Endpoint-CR an und sync't sie in die API (HTTP-Checks, TLS, Downtime). Im Discovery-Modus annotation (Standard) ohne dieses Flag kein Endpoint. |
polycrate_k8sapp_name |
{block.name} bzw. config.k8sapp_name |
Operator legt eine K8sApp-CR an (kind=external, discovery-source=ingress bzw. httproute) oder associert eine vorhandene Secret-/Block-App gleichen Namens. |
polycrate_k8sapp_byoa |
"true" |
API setzt K8sApp.byoa=true (CatalogueApp/Billing). Nur auf ingress-/httproute-sourced CRs; bestehende Block-Apps werden nicht umgeflaggt. |
polycrate_endpoint_path |
Readiness-httpGet.path, sonst Liveness, sonst / (Gateway: sonst gateway.path) |
Health-Check-Pfad des Endpoints |
Gleicher Hostname im selben Namespace: HTTPRoute gewinnt den Endpoint-CR; Ingress holt ihn nicht zurück. Ingress und Gateway bleiben im Block gegenseitig exklusiv.
Ergebnis: die App erscheint in der Polycrate API als K8sApp mit zugeordneten Endpoints. Claimed inkl. BYOA erzeugt Downtimes; nur polycrate_endpoint_monitor ohne polycrate_k8sapp_name bleibt unclaimed und erzeugt keine App-Downtime. Details: Endpoint-Monitoring.
workloads:
web:
ingress:
enabled: true
host: shop.acme.example
# optional overrides:
# endpoint_monitor: false
# k8sapp_name: acme-shop-frontend
# endpoint_path: /readyz
# oder Gateway statt Ingress (gegenseitig exklusiv):
# gateway:
# enabled: true
# host: shop.acme.example
# endpoint_path: /readyz
ingress.annotations und gateway.annotations werden danach gemerged und können einzelne Keys überschreiben. Gateway-Overrides: gateway.endpoint_monitor, gateway.k8sapp_name, gateway.endpoint_path.
Skalierung und Disruption (Faktor 8 + 9)
workloads:
web:
replicas: 2
hpa:
enabled: true
min_replicas: 2
max_replicas: 8
cpu_utilization: 70
pdb:
enabled: true
max_unavailable: 1
HPA und PDB gelten für Deployment und StatefulSet.
RBAC und NetworkPolicy (Faktor 15)
ServiceAccount default an (service_account.create, Name {block.name}).
rbac:
roles:
read-config:
rules:
- apiGroups: [""]
resources: [configmaps]
verbs: [get, list]
role_bindings:
read-config:
role: acme-shop-read-config
cluster_roles / cluster_role_bindings analog. Subjects default = Block-ServiceAccount.
network_policies:
default:
policy_types: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: envoy-gateway-system
ports:
- protocol: TCP
port: 8080
Default-podSelector: app.kubernetes.io/instance={{ block.name }}.
Telemetrie (Faktor 14) und Logs (Faktor 11)
metrics:
enabled: true
vmpodscrape:
enabled: true
path: /metrics
port: http
Erzeugt ein VMPodScrape (operator.victoriametrics.com/v1beta1) pro Deployment/StatefulSet/DaemonSet. Override pro Workload: workloads.web.metrics.path / port.
Die Anwendung schreibt Logs nach stdout/stderr. Die Plattform sammelt sie in VictoriaLogs. Kein Loki-Sidecar, kein Prometheus-ServiceMonitor in diesem Block.
Backing Services (Faktor 4)
BYOA enthält keine Datenbank. Geschwister-Blöcke anbinden:
- name: acme-shop-db
from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg
- name: acme-shop
from: cargo.ayedo.cloud/ayedo/k8s/byoa
config:
env:
- name: DATABASE_URL
value: postgres://acme-shop-db-rw.acme-shop.svc:5432/shop
env_from:
- secretRef:
name: acme-shop-db-app
Staging vs. Production ist eine Config-Änderung (Hosts, Secret-Namen, Replicas) — nicht ein zweites Template (Faktor 10).
Security-Defaults
security_context:
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
pod_security_context:
seccompProfile:
type: RuntimeDefault
Images, die als Root starten (z. B. traefik/whoami), brauchen einen Workload-Override (runAsUser: 65534). Produktionsimages sollen USER im Dockerfile setzen.
Private Registry:
image_credentials:
enabled: true
registry: cargo.ayedo.cloud
username: "{{ workspace.secrets.cargo.username }}"
password: "{{ workspace.secrets.cargo.password }}"
Was BYOA nicht tut
- Images bauen oder pushen (Faktor 5: Build liegt in der CI)
- PostgreSQL, Valkey oder Object Storage einbetten
- Secret-Werte auto-generieren
:latestimplizit setzen- Ingress oder Gateway default aktivieren
- ein Git-Repository ausliefern (Distribution über den Hub)
15-Factor-Zuordnung
Die 15-Factor App erweitert die klassischen 12 Faktoren um API First, Telemetry und Authentication & Authorization. Detailseite in dieser Sektion: 15-Factor Apps.
| Faktor | Anwendung | BYOA |
|---|---|---|
| 1 Codebase | Ein Repo, viele Deploys | Eine Block-Definition, viele Workspace-Instanzen. Kein Git-URL am Block. |
| 2 Dependencies | Im Image (go.mod, …) |
image.tag / digest pinnen. image_credentials für private Registries. |
| 3 Config | Nichts Umfeld-spezifisches im Image | env, env_from, ConfigMaps, Secrets, Checksum-Rollouts |
| 4 Backing Services | URLs, austauschbar | Geschwister-Blöcke, keine embedded DB |
| 5 Build, Release, Run | CI baut das Image | BYOA = Release (Image + Config) + Run (install) |
| 6 Processes | Zustandslos | Default Deployment. State in CNPG/Valkey/RustFS |
| 7 Port Binding | Lauschen im Prozess | ports → Container-Port + Service |
| 8 Concurrency | Nach Prozesstyp skalieren | Map-Keys web / worker / nightly, HPA |
| 9 Disposability | Schnell start/stop | Probes, Grace Period, PDB, Pre-Jobs disposable |
| 10 Dev/Prod Parity | Dasselbe Image | Maps in der Workspace-Config überschreiben |
| 11 Logs | stdout/stderr | VictoriaLogs auf der Plattform |
| 12 Admin Processes | One-off in derselben Env | kind: Job, hook: pre / post, CronJob |
| 13 API First | OpenAPI/gRPC in der App | HTTP/gRPC-Port + Edge-Route; CRDs via extra_manifests |
| 14 Telemetry | /metrics, Traces, Health |
VMPodScrape → VictoriaMetrics |
| 15 AuthN/Z | OIDC/OAuth2/JWT in der App | TLS am Edge, SA+RBAC, NetworkPolicy, Non-Root |
Häufige Fehler: Config im Image, PVC als „Datenbank“, :latest, nicht-idempotente Pre-Jobs, Ingress und Gateway gleichzeitig, Prometheus/Loki statt VictoriaMetrics/VictoriaLogs.
Weiterführend
- Hub-Block inkl.
examples.polyund README: ayedo/k8s/byoa - 15-Factor Apps · ohMyHelm Übersicht
- Polycrate Blöcke · Delivery Operations
- Erste Schritte