Implement Services non-converged compatible

This page contains information how to implement services, so that they are compatible with a non-coverged cluster.

While most of the things are handled automatically, there are some edgecases that need to be caught by the engineer implementing the service.

Kube Objects

References

References in an Object will always be evaluated on the control plane where Crossplane is running.

It is theoretically possible to reference any arbitrary object in a kuberenetes cluster, the references should always point to a Crossplane Managed Resource, so we ensure that it actually exists on the control plane.

By default, if no kind and apiVersion is specified, references will default to provider-kubernetes' Objects.

Example: Kube Object references
xRef := xkube.Reference{
	DependsOn: &xkube.DependsOn{
		Name: comp.GetName() + "-cluster",
	},
}

// Is equal to this
xRef := xkube.Reference{
	DependsOn: &xkube.DependsOn{
		Kind: "Object",
		APIVersion: "kubernetes.crossplane.io/v1alpha2",
		Name: comp.GetName() + "-cluster",
	},
}

// This won't work in a non-converged scenario, because the instance namespace doesn't exist on the control plane.
xRef := xkube.Reference{
	DependsOn: &xkube.DependsOn{
		Kind: "SGCluster",
		APIVersion: "kubernetes.crossplane.io/v1alpha2",
		Name: comp.GetName() + "-cluster",
		Namespace: comp.GetInstanceNamespace(),
	},
}

Connection Details

In contrast to references, the connection details are evaluated on the target service cluster. So it’s still possible to expose any arbitrary fields as connection details. This can even be used instead of a dedicated observer object.

Example: How to user connection details on a kube object
// Still works as always
obj.Spec.ConnectionDetails = []xkubev1.ConnectionDetail{
	{
		ToConnectionSecretKey: PostgresqlPassword,
		ObjectReference: corev1.ObjectReference{
			APIVersion: "v1",
			Kind:       "Secret",
			Namespace:  comp.GetInstanceNamespace(),
			Name:       comp.GetName(),
			FieldPath:  "data.superuser-password",
		},
	}
}

// Get the connection details
cd, err := svc.GetObservedComposedResourceConnectionDetails(comp.GetName()+"myobj")
if err != nil {
	return ...
}

Nested Composites

There’s logic in the runtime that will automatically set the same providerconfig on all managed resources for any given managed resource in a composition.

However, this does not work for nested composites. Because composites don’t have providerConfigRefs in their spec. So we need to ignore the providerconfig for the composite itself, but we have to ensure that the nested service knows about the providerConfig as well.

Example: How to deploy a nested composite
// We have to ignore the provideconfig on the composite itself.
pg := &vshnv1.XVSHNPostgreSQL{
	ObjectMeta: metav1.ObjectMeta{
		Name: a.comp.GetName() + PgInstanceNameSuffix,
		Labels: map[string]string{
			runtime.ProviderConfigIgnoreLabel: "true",
		},
	},
	Spec: vshnv1.XVSHNPostgreSQLSpec{
		Parameters: *params,
		ResourceSpec: xpv1.ResourceSpec{
			WriteConnectionSecretToReference: &xpv1.SecretReference{
				Name:      PgSecretName,
				Namespace: a.comp.GetInstanceNamespace(),
			},
		},
	},
}

// But pass the parent's provider config properly to the instance.
if v, exists := a.comp.GetLabels()[runtime.ProviderConfigLabel]; exists {
	pg.Labels[runtime.ProviderConfigLabel] = v
}

ProviderConfig Handling

As mentioned in the previous chapter, there’s logic in the function runtime that will automatically inject any given providerConfig set in the appcat.vshn.io/provider-config label on a composite.

In general to specify what providerConfig any given composite should follow, the appcat.vshn.io/provider-config label has to be set. Its value should be the name of any deployed providerConfig on the control plane.

Please note that providerConfigs need to exist for all relevant providers for any given composite, those are usually provider-helm and provider-kubernetes.

If the label is set, then the function runtime will inject that particular providerConf name into each managed resource in the composition.

There are however some special cases where an ignore label has to be set on the managed resource:

  • User and Database management for PostgreSQL and MariaDB. The comp functions create adhoc providerConfigs based on the connection details for the provider-sql. Because they are bound to the specific instance.

  • Nested Composites, as described above. Composistes themselves don’t have a schema for the providerConfig so it’s actually not possible to set it on a composite itself.

  • Object Buckets, they usually connect to a globally available Object storage instance, so the default provideConfig should apply.