Skip to main content

Publish the catalog

As a platform operator, publish the classes, plans, and connectivity options that tenants can select. A family can have multiple classes, such as single-node and replicated variants. Each class can have multiple plans. It lives in the fleet repository under platform/kube-dc-services-catalog/ and reaches a cluster through the Flux Kustomization services-catalog, which the cluster opts into with clusters/<name>/services-catalog.yaml. Nothing in the catalog is edited by hand on a cluster.

Layout​

PathContent
tenant-native.yaml, postgresql.yamlThe base: the connectivity class and the PostgreSQL bundle, class and plans
components/<family>/One component per additional family, such as mysql, valkey, or forgejo. Each carries the ServiceFamilyBundle, the ManagedServiceClass and the family's plans
components/shared-service-plans/The Development and Production plans for PostgreSQL, Kafka and Valkey, with the sizing table in its README. The MySQL, MariaDB and ClickHouse components define their own Development and Production pairs.
components/data-plane/Registers the cluster's own data plane
components/postgresql-gateway/, components/postgresql-public-loadbalancer/Exposure entitlements, where the cluster has a shared TLS listener or a public address pool
components/console*/Console publication of plans on clusters that predate the annotation being the default
revisions.lockOne row per cluster, kind and name with the revision and the digest of the rendered content

A cluster selects the components it offers:

components:
- components/data-plane
- components/mysql
- components/mariadb
- components/clickhouse
- components/valkey
- components/kafka
- components/shared-service-plans

Keep prune: false on the Kustomization. Removing a file must never delete a class or plan that a running instance still references.

What a plan says​

A plan is the product decision for one class. The fields an operator sets:

FieldDecides
classRef, revisionThe class, and the plan's own revision string
engineVersion, allowedImagesThe engine line the plan runs, and the exact images upgrades may move to
capacity.cpu, capacity.memory, capacity.storageThe per-member defaults
capacity.computeBounds, capacity.maxStorageWhat tenants may choose at creation and through Resize and ExpandStorage; a plan without bounds is fixed-size
capacity.storageRole, capacity.allowedStorageClassesThe logical storage role (database) the data plane maps to a class, and the other classes tenants may pick
capacity.allocationProtocol: v1Durable capacity accounting; required for Resize and RestoreInPlace
topologyEither min/max/defaultInstances, or min/max/defaultShards with min/max/defaultReplicasPerShard for sharded families; plus ha, synchronousStandbys, durability
operations.allowed, operations.autoApproveWhich operation types tenants may request and which run without an operator's approval
maintenanceThe recurring window (day, startHour, durationMinutes, UTC) for NextPlanWindow operations and deferred declarative changes
backupenabled, schedule, retentionDays, the retention and interval bounds, pitr, and the object store. See Storage roles and backups
exposureallowPublicLoadBalancer, and the shared gateway listener for PostgreSQL direct TLS
credentialsallowExistingUsers, allowBreakGlass, and the OpenBao delivery switches
allowedPlacementModes, allowedConnectivityClasses, allowedDeletionPolicies, minIsolationThe placement contract; every published shared plan allows only ProviderShared, tenant-native and NamespaceSharedNodes
maxInstancesPerProjectA per-Project quota; 0 means unlimited. The shared plans use 5, and 3 for the HA MariaDB and ClickHouse plans
support.tier, support.responsibilityTextWhat the console shows as the plan's responsibilities and limits
metersThe meters billed for the plan
disabledAccept no new services; existing ones keep running

Defaults are starting sizes, not performance guarantees. All shared plans expose computeBounds and maxStorage; which of those a tenant can change later depends on the family (MySQL and ClickHouse are sized once).

Console publication​

A plan appears in the tenant console when it carries the annotation services.kube-dc.com/console: enabled. This is publication, not entitlement: an unannotated plan still accepts manifests that name it. Hide a plan you have not qualified on your installation with disabled, and retire an old plan by setting the annotation to disabled, never by deleting the object while an instance references it.

The console groups a class's published plans into tiers by name and shape: *-development as Dev, *-production as Production, and a plan whose topology is highly available with more than one member as HA.

Storage roles and backups​

A plan asks for the storage role database, not a class name, because class names differ between installations. The data plane maps it:

SERVICES_STORAGE_ROLES={database: rbd-vm}
SERVICES_STORAGE_BUDGETS={rbd-vm: 500Gi}

Map the role to a class that supports volume expansion; local-path storage does not, and ExpandStorage is refused on it.

Backups go to the Project's own bucket by default: backup.objectStoreSource: ProjectBucketClaim with objectBucketClaimName: db-backups. Each Project has its own backup bucket. objectStoreEndpoint must be the endpoint the pods reach, https://<S3 hostname>, not the in-cluster service. Plane instead uses the plan's own endpoint and bucket with the cell's credential. The shared plans back up daily at 02:00 UTC and keep 7 days (Development) or 14 days (Production), with tenant-selectable retention of 1 to 35 days and schedules no more frequent than hourly.

Revisions​

An instance pins the class and plan revision strings it was created with, and nothing fences plan content under an unchanged revision. Every change to a cluster's rendered catalog therefore needs a new revision, either by bumping the literal in the manifest or by overriding one cluster's /spec/revision with a JSON patch in its services-catalog.yaml. revisions.lock records the digest of each rendered object per cluster; the pre-push gate refuses content that changed under an unchanged revision. After editing:

scripts/services-catalog-render-test.sh --update # refreshes revisions.lock
scripts/services-catalog-render-test.sh # the gate

A class carries spec.blueprint.expectedDigest. When the runner's compiled blueprint of that family does not match, the class stops being Verified and new placements are refused while running instances keep their pins. Regenerate the digest in the services repository with go run ./hack/blueprint-digest <family> when the family code changes, and move SERVICES_RUNNER_TAG, the bundle's revision and the class in one commit.

Extend the catalog​

The catalog is not a fixed list of engines. An additional service requires a family adapter, parameter schema, credential and endpoint contracts, and declared operation support. Its bundle must pin compatible runner and operator versions. Define capacity, isolation, connectivity, usage meters, and recovery responsibilities before you publish a plan.

Catalog entries alone do not implement a service. For any additional database, workflow system, or application, qualify its integration on the target infrastructure first. Publish only implemented operations. Describe unsupported recovery or scaling paths in the plan's responsibility statement.

Add a family to a cluster​

  1. Install or attest the engine operator (see the operator table on Enabling managed services) and list the family in SERVICES_FAMILIES; add it to SERVICES_UNMANAGED_OPERATORS when Flux, not the bundle, owns the operator.
  2. Select components/<family> in the cluster's services-catalog.yaml.
  3. Wait until kubectl get servicedataplane <plane> reports the bundle ready and the class Verified.
  4. Create a test service and verify a binding with an application client.
  5. Test each operation that the plan offers, including recovery where supported.
  6. Test access restrictions, quotas, deletion, and retained resources.
  7. Publish the console annotation only after the checks pass.