Decision on API Design
This decision page is currently still a WIP. |
Problem
This decision is about how to define an identity and "a common look" as mentioned here.
The ideal way would be, that we can re-use the exact same XRD for each set of services. For example all implementations for PostgreSQL have exactly the same API for the user. Due to the nature of the product, not all implementations provide the same functionality. The set of available features is most likely not the same between most providers. Also with Crossplane we’re a bit limited on what’s possible with translating the source API spec into the target API spec:
-
No conditionals in compositions
-
Only a limited set of transformations
-
Can’t create random values
This is not a complete list of limitations. |
Requirements
-
APIs for a set of similar services feel similar
-
Only minimal configuration is needed for a default case
-
Unique features of a service provider can be leveraged
-
Expose as few options as possible and extend as we go
Proposals
Proposal 1: Use same XRD with provider specific fields
There’s exactly one XRD for a set of services. This XRD contains a minimal set of common parameters, as well as a field for provider specific configurations.
apiVersion: appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
majorVersion: '14'
pgSettings: {}
exoscale:
plan: hobbyist-2
type: pg
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
vshn:
minorVersion: '4'
- Advantages
-
-
We can use the same XRD
-
Adding new providers to the list is easy
-
- Disadvantages
-
-
We don’t really have a "common look"
-
The user needs to know some specifics of the underlying service provider.
-
There may only be 1–3 common fields for a given service
-
Proposal 2: Separate XRDs with Convention
This approach will have different XRDs for each provider. However, they will follow the same convention, as far as possible. All the top level fields will be the same, the differences will be apparent in the sub-fields. Thus creating groups of fields. These groups can be different for any given service type. They will also have the same kind, but a different APIVersion.
If a provider within a service type doesn’t support all the groups, then we’ll omit the group as a whole. This will also make it clear at one glance that the given provider doesn’t support this feature.
The fields below a group are not specified and depend on what the provider is capable of. Each group describes a very generic part of a service, like its size or maintenance.
It also depends on the provider if adding grouping makes actually sense, see examples.
# Exoscale example
apiVersion: exoscale.appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
service:
version: '14'
type: pg
settings: {}
size: (1)
plan: hobbyist-2
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
# Converged example
apiVersion: vshn.appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
service:
version: '14.4'
settings: {}
pgbouncer:
settings: {}
size: (1)
cpu: 2
memory: 8Gi
disk: 200Gi
encrypted: true
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
# Bucket without any grouped fields
apiVersion: appcat.vshn.io/v1
kind: ObjectBucket
metadata:
name: my-cool-object-storage-bucket
namespace: my-namespace
spec:
parameters:
bucketName: my-bucket-change-name
region: rma (2)
# Managed Gitlab
apiVersion: vshn.appcat.vshn.io/v1
kind: Gitlab
metadata:
name: gitlab-prod
spec:
parameters:
name: gitlab-prod
service:
url: https://gitlab.example.com
runners: true
pages: true
size: (1)
runners: 3
disk: 300Gi
ha: true
clients: (3)
- 192.168.1.0/24
- 192.168.2.0/24
#no maintenance or backup as of now
1 | Size might be an example top-level parameter that can be used in many services, although its subfields are provider-specific. If a provider doesn’t make use of a given top-level parameter, then it may simply be omitted from the API scheme. |
2 | Object bucket is a very simple service that doesn’t provide many configuration options. Using any grouped top-level parameters doesn’t make sense. |
3 | Clients might be an example of a top-level parameter that only finds use for some very specific services.
|
Proposal 3: Composed XRDs
There’s a base XRD that handles the common configurations. All provider specific parameters are put in a separate XRD.
This idea may not be possible with Crossplane, but it’s here for completeness’ sake.
apiVersion: appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
version: '14'
pgSettings: {}
apiVersion: exoscale.appcat.vshn.io/v1
kind: PostgreSQLConfig
metadata:
name: my-instance
spec:
parameters:
instanceRef: my-instance
plan: hobbyist-2
type: pg
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
- Advantages
-
-
Clear separation of base and specific configuration parameters
-
- Disadvantages
-
-
"same look" will only apply to the base XRDs
-
Pretty complicated to use for the end-user
-
There may only be 1–3 common fields for a given service
-
Proposal 4: Same XRD and Parse the Input
We use the same XRD for every provider. Most fields are simply strings, and they will then be parsed in the composition.
For example one provider has hobbyist-2
as a valid size, while other need CPU/Memory/Disk, this could be represented by custom-2-8-200
.
This is inspired by GCP Terraform.
# Exoscale example
apiVersion: appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
version: '14'
pgSettings: {}
# we pass the names of the plans for exoscale
size: hobbyist-2
type: pg
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
# vshn converged example
apiVersion: appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
# vshn converged might need the minor version, too
version: '14.4'
pgSettings: {}
# we derive the size of the instance from this string
# custom-$cpu-$memory-$diskspace
size: custom-2-8-300
# vshn converged may not have a type
type: ''
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
- Advantages
-
-
Exact same API for all services of the same set
-
- Disadvantages
-
-
Awkward to use, there needs to be a lot of documentation
-
The complexity of the compositions increases drastically with the parsing rules
-
Proposal 5: Group Specific Settings under One Field
This is a sub-variant of proposal 2.
Everything that has provider specific naming is grouped under a field called providerSpecific
.
If a provider doesn’t support a given top-level parameter (for example maintenance
), then it’s omitted.
# Exoscale
apiVersion: exoscale.appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
providerSpecific:
version: "14"
service:
type: pg
settings: {}
plan: hobbyist-2
# VSHN Converged
apiVersion: vshn.appcat.vshn.io/v1
kind: PostgreSQL
metadata:
name: my-instance
spec:
parameters:
name: my-instance
maintenance:
day: 2
hour: 22
minute: 0
backup:
hour: 20
minute: 0
providerSpecific:
version: "14.4"
service:
settings: {}
resources:
cpu: 2
memory: 2048
disk: 200G
- Advantages
-
-
All provider specific fields are grouped
-
- Disadvantages
-
-
There may only be 1–3 common fields for a given service, shifting everything under the
providerSpecific
field -
Increases the nesting
-
Decision
We agreed on Proposal 2.
Additional points we’ve agreed on:
-
Exposing only needed fields, add more as needed
-
Sensible defaults, minimize required parameters.
To minimize confusion when querying the claims. The kind should also contain the provider as a pre-fix:
apiVersion: acmecorp.appcat.vshn.io/v1
kind: AcmecorpPostgreSQL
Rationale
Proposal 2 provides the most flexibility for modelling the various limitations and differences that various providers might have. All other proposals are hard definitions what the API should look like and are not a convention. This makes it hard to tailor the various provider limitations and differences to fit the definitions.
To achieve the identity and same look for the various providers and services a flexible convention makes the most sense.
Avoiding Confusion with Specific Kinds
In Kubernetes there’s ambiguity if there are multiple CRDs with the same kind
but different apiversion
fields.
Given a Kubernetes cluster with these CRDs:
-
foo.a.io
-
foo.b.io
If a user invokes kubectl get foo
only CRs of type foo.a.io
are returned [1].
This could lead to "missing" CRs from a user perspective
Addendum 1 Exposing Versions
The main point for this approach is UX.
Users get instant feedback if their desired version isn’t supported.
Available versions as directly visible via kubectl
and no other documentation has to be consulted.
One drawback for this is, that versions need to be adjusted if a provider changes them. This issue is much smaller for VSHN Managed Services, as new versions are controlled by ourselves.