How to Implement a new Service Offering
This page describes the steps which are necessary to implement a new service offering based on Crossplane and the crossplane-service-broker.
We use the fictional Foo
service in the following examples and a fictional Helm chart foo-chart
.
Create Crossplane Resources
The main work to implement a new service offering is creating the required Crossplane resources. This includes a CompositeResourceDefinition (XRD) and one Composition per offered plan.
See the Crossplane Service Broker explanation on how these resources map to the open service broker concepts.
See the Crossplane documentation on more general information regarding Crossplane and its different resources.
CompositeResourceDefinition (XRD)
The XRD resource defines a new service offering.
You can use a name of your choosing, the Crossplane best practice defines a naming scheme of Composite<NAME>Instance
.
Metadata
Make sure the following labels are set (see metadata):
-
service.syn.tools/id
: Generate a new UUID -
service.syn.tools/name
: Name of the new service offering
To add further metadata you can use the following annotations:
-
service.syn.tools/description
: Description for the new service offering -
service.syn.tools/metadata
: JSON object with further metadata
Connection Details
Define which information an application needs in order to be able to connect and use this service offering by using the .spec.connectionSecretKeys
list.
Usually this includes an endpoint (IP address or hostname), a port as well as credentials (username/password).
Printer Columns
Defining printer columns can help in troubleshooting by showing relevant information.
Configure printer columns for the Plan
and Cluster
.
Example
kind: CompositeResourceDefinition
metadata:
name: compositefooinstances.syn.tools
labels:
service.syn.tools/id: 6ca63cdb-0cfa-4c5e-b080-72f22ff5f3e6 # uuidgen | tr '[:upper:]' '[:lower:]'
service.syn.tools/name: foo-k8s
service.syn.tools/updatable: 'true'
annotations:
service.syn.tools/description: Foo high performance database for big data machine learning.
service.syn.tools/metadata: |
{
"displayName": "Foo DB on K8s",
"version": "1.3.37"
}
service.syn.tools/tags: |
["foo", "ml", "bigdata", "bar"]
spec:
connectionSecretKeys:
- endpoint
- port
- username
- password
group: syn.tools
names:
kind: CompositeFooInstance
plural: compositefooinstances
versions:
- name: v1
referenceable: true
served: true
additionalPrinterColumns:
- jsonPath: .metadata.labels['service\.syn\.tools/plan']
name: Plan
type: string
- jsonPath: .metadata.labels['service\.syn\.tools/cluster']
name: Cluster
type: string
Compositions
The Compositions define the "building blocks" how the service offering is being provisioned. See the Crossplane documentation for further information how they work and play together with XRDs and XRs.
Each offered plan of a service is defined in a Composition.
Metadata
The name of a composition must be its UUID. Make sure the following labels are set (see metadata):
-
service.syn.tools/plan
: Name of the plan -
service.syn.tools/cluster
: Name of the cluster this plan should be provisioned -
service.syn.tools/id
: UUID of the service this plan belongs to -
service.syn.tools/name
: Name of the service this plan belongs to -
service.syn.tools/updatable
: If the service instances can be updated
To add further metadata you can use the following annotations:
-
service.syn.tools/description
: Description for this plan -
service.syn.tools/metadata
: JSON object with further metadata
Resources
A Composition defines a list of K8s resources which should be created for each instance of this plan.
These resources together build the provisioned instance and usually consist of Release
resources which the provider-helm then installs accordingly.
Patches can be used to further parametrize the resources and use information on the XR as input for further templating.
Example
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: 43905323-ed5f-466a-9d7f-ddc16cb74864 # uuidgen | tr '[:upper:]' '[:lower:]'
labels:
service.syn.tools/cluster: prod-cluster-01
service.syn.tools/id: 6ca63cdb-0cfa-4c5e-b080-72f22ff5f3e6 # This is the ID from 'compositefooinstances.syn.tools' above
service.syn.tools/name: foo-k8s
service.syn.tools/plan: small
service.syn.tools/updatable: "true"
annotations:
service.syn.tools/description: Foo instance small size
service.syn.tools/metadata: |
{
"displayName": "Small",
"memory": "1Gi",
"storageCapacity": "8Gi"
}
spec:
compositeTypeRef:
apiVersion: syn.tools/v1
kind: CompositeFooInstance
writeConnectionSecretsToNamespace: crossplane-system # Namespace to collect all connection secrets
resources:
- connectionDetails:
- fromConnectionSecretKey: endpoint
- fromConnectionSecretKey: port
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
base:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
spec:
# Read back information from provisioned K8s resources in the target namespace
connectionDetails:
- apiVersion: v1
kind: Service
name: foo-master
fieldPath: status.loadBalancer.ingress[0].ip
toConnectionSecretKey: endpoint
- apiVersion: v1
kind: Service
name: foo-master
fieldPath: spec.ports[0].port
toConnectionSecretKey: port
- apiVersion: v1
kind: Secret
name: foo-admin
fieldPath: data.username
toConnectionSecretKey: username
- apiVersion: v1
kind: Secret
name: foo-admin
fieldPath: data.password
toConnectionSecretKey: password
writeConnectionSecretToRef:
namespace: crossplane-system
forProvider:
chart:
name: foo-chart
repository: https://charts.example.com
version: 1.3.37
values:
fullnameOverride: foo
service:
type: LoadBalancer
resources:
requests:
cpu: 1000m
memory: 1Gi
limits:
cpu: 2000m
memory: 1Gi
rollbackLimit: 3
patches:
- fromFieldPath: metadata.labels
- fromFieldPath: metadata.annotations
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.forProvider.namespace
- fromFieldPath: metadata.labels[service.syn.tools/cluster]
toFieldPath: spec.providerConfigRef.name
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.writeConnectionSecretToRef.name
transforms:
- string:
fmt: "%s-foo"
type: string
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.connectionDetails[0].namespace
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.connectionDetails[1].namespace
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.connectionDetails[2].namespace
- fromFieldPath: metadata.labels[crossplane.io/composite]
toFieldPath: spec.connectionDetails[3].namespace
Commodore Component
To simplify the creation of these Crossplane resources the spks-crossplane component exists. It will generate the required resources based on the configured input in the configuration hierarchy.
This approach especially helps in defining multiple plans in order to keep the config more DRY and maintainable.
Example
This example setup will generate the same Crossplane resources as showcased in the previous examples.
parameters:
spks_crossplane:
serviceDefinitions:
foo-k8s:
uuid: 6ca63cdb-0cfa-4c5e-b080-72f22ff5f3e6
description: Foo high performance database for big data machine learning.
metadata:
displayName: Foo DB on K8s
version: 1.3.37
tags:
- foo
- ml
- bigdata
- bar
updatable: "true"
xrd: CompositeFooInstance
connectionSecretKeys:
- endpoint
- port
- username
- password
versions:
- name: v1
served: true
referenceable: true
additionalPrinterColumns:
- jsonPath: .metadata.labels['service\.syn\.tools/plan']
name: Plan
type: string
- jsonPath: .metadata.labels['service\.syn\.tools/cluster']
name: Cluster
type: string
baseComposition:
writeConnectionSecretsToNamespace: crossplane-system
resources:
01_foo-helm-chart:
connectionDetails:
- fromConnectionSecretKey: endpoint
- fromConnectionSecretKey: port
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
base:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
# See example above for further details
...
plans:
small:
uuid: 43905323-ed5f-466a-9d7f-ddc16cb74864
description: Foo instance small size
cluster: prod-cluster-01
metadata:
displayName: Small
memory: 1Gi
storageCapacity: 8Gi
resources:
01_foo-helm-chart:
base:
spec:
forProvider:
values:
configmap: |
maxmemory 768mb
resources:
requests:
cpu: 1000m
memory: 1Gi
limits:
cpu: 2000m
memory: 1Gi
medium:
uuid: 4e4045a3-7099-4645-a3a4-50f3df10a7a5
description: Foo instance medium size
cluster: prod-cluster-02
metadata:
displayName: Medium
memory: 2Gi
storageCapacity: 16Gi
resources:
01_foo-helm-chart:
base:
spec:
forProvider:
values:
configmap: |
maxmemory 1512mb
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 2Gi
Update Broker Implementation
Some code changes on the crossplane-service-broker are necessary to introduce a new service offering. To support a new service offering the ServiceBinder
interface must be implemented and optionally the ProvisionValidater
.
The simplest example is the implementation for the Redis service as it only implements the GetBinding()
function.
To make use of the newly implemented interface, the ServiceBinderFactory()
function here must be updated with the name and constructor of the new service.
The following is an example pull request to implement the Foo service: github.com/vshn/crossplane-service-broker/pull/39