🏛️ Konventionen¶
Jeder Block MUSS ein kind haben¶
Mögliche Werte für kind sind:
k8sappfür Apps die in Kubernetes installiert werdenk8clusterfür Kubernetes Cluster, egal wie sie installiert werdenlinuxappfür Apps die in Linux installiert werdengeneric
Beispiel¶
Tip
Dynamische Blöcke, die via from: von Blöcken mit kind: k8sapp erben, exportieren kind: k8sappinstance. Damit wird sichtbar, dass diese Blöcke eine in Kubernetes installierte Variante des ursprünglichen Codes bereitstellen – also eine App-Instanz im Cluster.
App-Blöcke: SOLLTEN type und flavor haben¶
Beispiele für type:
dbkvmqs3web
Type steht für die übergeordnete Kategorie in die der App-Block fällt: ist es eine Datenbank, ein Key-Value-Store, etc.
Beispiele für flavor:
mysqlpostgresqlnatsredisminio/cephrubyonrailspythongolang
Flavor steht für die Implementierung des Type - mysql ist eine Variante vom Typ db (Database).
App-Blöcke: 1 Block = 1 App = 1 Chart = 1 Namespace¶
Dient ein Block dazu, eine einzelne App (Anwendung, Software, …) auf ein Zielsystem zu installieren (z.B. ingress-nginx in Kubernetes), muss sich der Block auf die Installation dieser einzelnen App beschränken.
Hat ein solcher App Block weitere Dependencies (z.B. PostgreSQL), müssen die Dependencies in separaten Blöcken installiert werden.
Ein App-Block darf außerdem nur 1 Helm Chart nutzen. In manchen Fällen ist es okay, die im Helm Chart definierten Dependencies (z.B. Redis) zu aktivieren - in solchen Fällen bleibt das "1 Block - 1 App" Prinzip intakt.
Ein Block muss sich außerdem auf einen Namespace beschränken und als Teil seiner Installations-Routine die Möglichkeit bieten, den Namespace anzulegen.
Beispiel¶
# block.poly
name: my-block
kind: k8sapp
config:
namespace: my-namespace
create_namespace: true
chart:
name: my-chart
oci: false
auth:
enabled: false
username: ""
password: ""
registry: ""
version: 0.0.1
repo:
url: oci://my.registry.com/helm
name: "my-chart"
Info
Das oben dargestellte Config-Schema ist Voraussetzung, um cargo.ayedo.cloud/ayedo/k8s/library als Library für Helm-Charts zu verwenden.
Kubernetes-Workspaces müssen Metriken und Logs exportieren¶
Die Logs und Metriken müssen entweder im Cluster selbst verfügbar gemacht werden, oder an ein zentrales Aggragator-Cluster übermittelt werden.
- Block für Metriken: https://hub.polycrate.io/block/ayedo/k8s/victoria-metrics-stack/
- Block für Logs: https://hub.polycrate.io/block/ayedo/k8s/vector/
Beispiel¶
# workspace.poly
- name: vector
from: cargo.ayedo.cloud/ayedo/k8s/vector
kubeconfig:
from: k8s
config:
dependencies:
victorialogs:
connection:
endpoint: https://logs.example.com/insert/elasticsearch
username: user
password: password
- name: victoria-metrics-stack
from: cargo.ayedo.cloud/ayedo/k8s/victoria-metrics-stack:0.6.10
kubeconfig:
from: k8s
config:
vm:
additionalremotewrite:
urls:
- url: "https://$USER:$PASSWORD@metrics.example.com/api/v1/write"
App-Blöcke MÜSSEN Monitoring und Backups implementieren¶
Für Monitoring sind folgende Ressourcen bereitzustellen:
- VMServiceScrape
- Grafana Dashboard ConfigMap
Für Backups sind folgende Ressourcen bereitzustellen:
- Velero BackupSchedule
Beispiel¶
# block.poly
name: my-block
kind: k8sapp
config:
monitoring:
enabled: false
vmservicescrape:
enabled: false
dashboard:
enabled: false
backups:
enabled: false
schedule: "* * * * *"
App-Blöcke MÜSSEN Kubernetes Manifeste exportieren¶
Installiert ein Block Ressourcen (z.B. Helm Charts oder einzelne Manifeste) in Kubernetes, müssen die finalen Manifeste in der Block Artifacts Directory abgespeichert werden.
Info
Nutzt ein Block cargo.ayedo.cloud/ayedo/k8s/library zum Installieren von Anwendungen, speichert Polycrate die generierten Manifeste und finalen Values automatisch in der Block-Artifact-Directory.
App-Blöcke MÜSSEN Polycrate Labels vererben¶
Polycrate fügt jedem Block unter block.labels einige Plattform-Labels hinzu, die Polycrate helfen, aktive Ressourcen, z.B. Anwendungen in Kubernetes, ihren ursprünglichen Blöcken und Workspaces zuzuordnen.
Folgende Labels setzt Polycrate automatisch:
name: my-block
kind: k8sappinstance
labels:
blocks.polycrate.io/kind: k8sappinstance
blocks.polycrate.io/name: my-block
k8sappinstances.polycrate.io/name: my-block
organizations.polycrate.io/name: my-org
workspaces.polycrate.io/name: my-workspace
Info
Die finalen Labels eines Blocks lassen sich mit polycrate block inspect $BLOCK_NAME einsehen.
Tip
dynamische Blöcke, die via from: von Blöcken mit kind: k8sapp ableiten, exportieren automatisch kind: k8sappinstance. Das kennzeichnet sie als laufende Instanz der App in einem Kubernetes-Cluster.
Diese Labels stehen in Ansible-basierten Blöcken unter block.labels zur Verfügung und müssen an Helm Charts und Kubernetes Manifeste vererbt werden.
Beispiel Helm Chart¶
Warning
Nicht alle Helm-Charts unterstützen die Vererbung von Labels. Falls das der Fall ist, kann auf die Vererbung verzichtet werden.
Beispiel Manifest¶
# clusterissuer.yaml.j2
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: my-cluster-issuer
labels: {{ block.labels }}
[...]
Blöcke MÜSSEN versioniert werden¶
Polycrate folgt einem Versionierungsschema basierend auf Conventional Commits. Conventional Commits ist eine Methodik Versionsnummern auf Basis von Commit-Messages, bzw. deren Typ (Type of Change), zu berechnen.
In Polycrate nutzen wir Git-Commits nur zur Persistierung finaler Stände des Workspaces in einem Git-Repository - einzelne Git-Commits haben aber keinen Einfluss auf die Versionierung eines Blockes.
Ein Block wird in der Regel nicht entwickelt wie reguläre Software. Die Entwicklung von Blöcken passiert immer im Kontext eines Workspaces - sobald ein Block-Autor der Meinung ist, seine Änderungen am Block sind komplett, erzeugt er eine neue Version, taggt diese mit einem Prefix passend zu der Art und des Umfangs seiner Änderungen am Block, und beschreibt die Anpassungen am Block. Am Ende pusht er die neue Version des Blocks mit polycrate block push … in eine OCI-kompatible Registry und macht ihn dadurch verfügbar für andere.
Wir nutzen die in Conventional Commits etablierten Typen und Mechanismen, um die Komplexität einer Block-Version zu beschreiben und damit den Nutzern des Blocks ein qualifiziertes Bild von Risiko und Komplexität eines Versionssprungs zu vermitteln.
Die folgende Tabelle beschreibt die empfohlene Verwendung von Prefixes einer Block-Version. Im nächsten Abschnitt wird erläutert, wo genau diese Form der Versionierung relevant ist.
| Type of Change | Beschreibung | Initial Version | New Version |
|---|---|---|---|
chore | keine wesentliche Änderung vorgenommen (z.B. nur Änderungen an der README oder Teilen des Blocks die keinen Einfluss auf das Gesamtverhalten des Blocks haben), abwärts-/aufwärts-kompatibel | 0.0.1 | 0.0.2 |
fix | ein Fehler wurde behoben oder eine unkritische Änderung vorgenommen, ggf. abwärtskompatibel (hängt von Datenbank-Migrationen u.ä. ab); aufwärts-kompatibel | 0.0.2 | 0.0.3 |
feat | ein neues Feature wurde eingebaut, ggf. abwärtskompatibel (hängt von Datenbank-Migrationen u.ä. ab); aufwärts-kompatibel | 0.0.3 | 0.1.0 |
breaking | eine nicht-abwärtskompatible oder kritische Änderung wurde vorgenommen; das ist vor allem dann der Fall, wenn ein Versionssprung eine Inkompatibilität und/oder manuellen Eingriff bedingt (Beispiel: manuelles Upgrade von Postgres 11 auf 14; oder: Config-Struktur hat sich geändert) | 0.1.0 | 1.0.0 |
Info
Neue Blöcken starten per Konvention bei Version 0.0.1.
Warning
Achtung: Im Gegensatz zu den Defaults von Conventional Commits führt jede Änderung an Polycrate-Blöcken zu einem Versionssprung. Selbst chore erhöht die Patch-Version.
Blöcke MÜSSEN ein CHANGELOG führen¶
Jede Änderung eines Blockes muss eine neue Version erzeugen. Jede neue Version eines Blockes muss einen Eintrag im CHANGELOG erzeugen.
Traditionell wurde das Changelog in einer eigenen Sektion der README Datei des Blockes geführt.
# My Block
Installiert [My App](https://my-app.com/) in Kubernetes.
---
## Changelog
- `0.1.0` feat: My App Helm Chart von 1.12.2 auf 1.14.2 aktualisiert.
- `0.0.1` feat: initial-commit.
Seit April 2025 wird jedoch das Führen des Changelogs in der Datei CHANGELOG.poly nach folgendem Format empfohlen:
- version: 0.0.2
type: fix
message: fixed a typo
description: "Hier ist Platz für längere Texte, inklusive Markdown"
date: "2024-02-02"
email: user@example.com
- version: 0.0.1
type: feat
message: initial commit
description: ""
date: "2024-02-02"
email: user@example.com
1 Workspace = 1 Kubernetes Cluster¶
Pro Workspace sollte nur 1 Kubernetes Cluster verwaltet werden.
Prinzipiell ist es möglich, beliebig viele Cluster zu verwalten, der Übersicht und der Vermeidung von Fehlern halber wird aber empfohlen, die Anzahl an Kubernetes Clustern pro Workspace auf 1 zu limiteren.
Kubeconfig und Inventory¶
Das Workspace Inventory muss in Form eines yaml-formatierten Ansible-Inventory in der Datei inventory.yml in der Workspace Root liegen.
Die Workspace Kubeconfig muss in Form einer gültigen Kubeconfig in der Datei kubeconfig.yml in einer Block Artifact Directory liegen. Wird Kubernetes mit Hilfe eines Blocks installiert, muss der Block die Kubeconfig zur Steuerung des Clusters in seine Block Artifact Directory exportieren.
Per Konvention ist der Name des Blocks der die Kubeconfig bereitstellt k8s.
Beispiel¶
Ein Workspace MUSS eine Organization haben¶
Die Organization wird von Polycrate für das automatische Labelling von Ressourcen, die durch Blöcke erzeugt wurden, genutzt.
Beispiel¶
Jeder Block MUSS eine install und eine uninstall Action haben¶
Nuff said.
Beispiel¶
# block.poly
name: my-block
kind: k8sapp
config: {}
actions:
- name: install
playbook: install.yml
- name: uninstall
playbook: uninstall.yml
Block Config MUSS einem Schema folgen¶
Das Block Config Schema ist nicht bindend (d.h. die Polycrate CLI beendet die Ausführung nicht, wenn Werte nicht gesetzt sind), jedoch interagieren andere Teile des Ökosystems (z.B. Polycrate API) mit dem Schema, um aus einer Block-Definition abzuleiten, welche Funktion der Block hat, in welcher Plattform er läuft oder welche Endpunkte überwacht werden müssen.
kind: k8sapp¶
name: cr.ayedo.cloud/blocks/k8s/my-app
version: 0.0.1
kind: k8sapp
type: web # or: db|kv
flavor: python # or: postgresql|redis|golang|nodejs
icon_url: https://example.com/icon.svg
website_url: https://example.com/
display_name: My App
license: MIT
license_url: https://git.examlpe.com/license.md
documentation_url: https://docs.examlpe.com
releases_url: https://examlpe.com/releases
git_repository_url: https://git.examlpe.com
descrition: "This app is a good app"
supports_ha: false
app_version: 1.2.3
labels:
foo: bar
config: # TYPED
namespace: my-app # every k8sapp should have this
create_namespace: true # every k8sapp should have this
connection:
endpoint: my-app.com
port: 80
username: ""
password: ""
tls:
enabled: false
chart: # every k8sapp should have this
name: my-app
version: 1.2.3
oci: false # must be `true` if repo.uri starts with `oci://`
auth:
enabled: false
username: ""
password: ""
registry: ""
repo:
url: https://my.repo.com/charts/
name: my-app
app: # UNTYPED
signup:
enabled: false # true = Signup für alle aktiviert // false = Signup für Domains aktiviert
domains: []
verify_user_email: false
require_user_email: false
allow_invitation: true
admin:
enabled: false
token: extremely_long_admin_token_and_secure_too
replica_count: 1 # every k8sapp should have this
persistence: # every k8sapp should have this
enabled: false
size: 5Gi
access_mode: ReadWriteOnce
storage_class: "-"
update_strategy: # every k8sapp should have this
type: Recreate # or Rolling
ingress: # every k8sapp should have this
enabled: false
class: nginx
host: my-app.com
extra_annotations: {}
extra_labels: {}
extra_hosts: [] # ggf. mit /path?
tls:
enabled: false
letsencrypt:
issuer: letsencrypt-production
extra_manifests: [] # every k8sapp should have this; UNTYPED
dependencies:
postgres: # content will be replaced with the block from `from` if found; could be instanciated as a block on its own in the future
enabled: true
from: my-app-db
connection:
endpoint: postgresql
port: 5432
username: postgresql
password: postgresql
tls:
enabled: false
accept_invalid_certificate: false
accept_invalid_hostname: false
app:
database: postgresql
admin:
username: ""
password: ""
# url: postgresql://user:pass@postgresql:5432/database-name
monitoring:
enabled: false
vmservicescrape:
enabled: false
dashboard:
enabled: false
backup:
enabled: false
schedule: ""
extra_env:
- name: ENV_1
value: value1
resources:
limits:
memory: 256Mi
cpu: 200m
ephemeral-storage: 512Mi
requests:
memory: 128Mi
cpu: 100m
ephemeral-storage: 256Mi
actions:
- name: install
playbook: install.yml
- name: uninstall
playbook: uninstall.yml
Dependency: Postgres¶
name: postgres-db
from: cr.ayedo.cloud/blocks/k8s/cloudnative-pg-cluster
version: 0.0.1
flavor: postgresql
type: database
kind: k8sapp
labels:
foo: bar
config: # UNTYPED
connection:
endpoint: lol.com
port: 25
username: asdasd
password: asdasd
tls:
enabled: false
namespace: cloudnative-pg # every k8sapp should have this
create_namespace: false # every k8sapp should have this
chart: # every k8sapp should have this
name: cloudnative-pg
version: 1.2.4
oci: false
auth:
enabled: false
username: ""
password: ""
registry: ""
repo:
url: https://cloudnative-pg.github.io/charts/
name: cloudnative-pg
app: # UNTYPED
signup:
enabled: false # true = Signup für alle aktiviert // false = Signup für Domains aktiviert
domains: []
verify_user_email: false
require_user_email: false
allow_invitation: true
admin:
enabled: false
token: extremely_long_admin_token_and_secure_too
replica_count: 1 # every k8sapp should have this
persistence: # every k8sapp should have this
enabled: false
size: 5Gi
access_mode: ReadWriteOnce
storage_class: "-"
strategy: # every k8sapp should have this
type: Recreate
ingress: # every k8sapp should have this
enabled: false
class: nginx
host: vaultwarden.example.com
extra_hosts: [] # ggf. mit /path?
tls:
enabled: false
letsencrypt:
issuer: letsencrypt-production
extra_manifests: []
dependencies: {}
monitoring:
enabled: false
vmservicescrape:
enabled: false
backup:
enabled: false
schedule: ""
actions:
- name: install
playbook: install.yml
- name: uninstall
playbook: uninstall.yml