content/v2.0/guides/implementing-safe-start.md
This guide shows provider developers how to implement safe-start capability in their Crossplane providers. safe-start enables [disabling unused managed resources]({{<ref "disabling-unused-managed-resources">}}) through ManagedResourceDefinitions, improving performance and reducing resource overhead.
{{<hint "important">}} safe-start requires Crossplane v2.0+ and crossplane-runtime v2.0+. Implementing safe-start involves code changes that affect provider startup behavior. {{</hint>}}
safe-start changes how your provider handles CRD installation:
Without safe-start:
With safe-start:
Before implementing safe-start:
Add safe-start to your provider package metadata:
# package/crossplane.yaml
apiVersion: meta.pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-example
spec:
capabilities:
- safe-start
Update your main.go imports (see crossplane-runtime godoc for full API reference):
import (
// existing imports...
"k8s.io/apimachinery/pkg/runtime/schema"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"github.com/crossplane/crossplane-runtime/v2/pkg/controller"
"github.com/crossplane/crossplane-runtime/v2/pkg/gate"
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/customresourcesgate"
)
Add gate initialization in your main function:
func main() {
// existing setup code...
o := controller.Options{
// existing options...
Gate: new(gate.Gate[schema.GroupVersionKind]),
}
// Add CustomResourceDefinition to scheme for gate controller
if err := apiextensionsv1.AddToScheme(mgr.GetScheme()); err != nil {
panic(err)
}
// Setup controllers
if err := yourprovider.Setup(mgr, o); err != nil {
panic(err)
}
// Setup the CRD gate controller
if err := customresourcesgate.Setup(mgr, o); err != nil {
panic(err)
}
// start manager...
}
Create a gated setup function for each managed resource controller:
// SetupGated registers controller setup with the gate, waiting for the
// required CRD
func SetupGated(mgr ctrl.Manager, o controller.Options) error {
o.Gate.Register(func() {
if err := Setup(mgr, o); err != nil {
panic(err)
}
}, v1alpha1.MyResourceGroupVersionKind)
return nil
}
// Setup is your existing controller setup function (unchanged)
func Setup(mgr ctrl.Manager, o controller.Options) error {
// existing controller setup code...
}
Change your controller setup to use the gated versions:
// internal/controller/controller.go
func Setup(mgr ctrl.Manager, o controller.Options) error {
for _, setup := range []func(ctrl.Manager, controller.Options) error{
myresource.SetupGated, // Changed from myresource.Setup
// other gated setups...
} {
if err := setup(mgr, o); err != nil {
return err
}
}
return nil
}
The safe-start implementation uses a "gate" pattern:
customresourcesgate controller watches for CRD
creation/deletionTest safe-start behavior with this basic workflow:
# Install Crossplane v2.0+
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--set provider.defaultActivations={}
# Install your provider
kubectl apply -f provider.yaml
# Check that MRDs are created but inactive
kubectl get mrds
# All should show STATE: Inactive
# No CRDs should exist yet
kubectl get crds | grep yourprovider.m.crossplane.io
# Should return no results
# Create activation policy
kubectl apply -f - <<EOF
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: ManagedResourceActivationPolicy
metadata:
name: test-activation
spec:
activate:
- "myresource.yourprovider.m.crossplane.io"
EOF
# Verify activation worked
kubectl get mrd myresource.yourprovider.m.crossplane.io
# Should show STATE: Active
# CRD should now exist
kubectl get crd myresource.yourprovider.m.crossplane.io
Cause: gate waits for CRDs that never become active.
<!-- vale Google.WordList = NO -->Solution: check that Crossplane activated MRDs and created CRDs:
<!-- vale Google.WordList = YES -->kubectl get mrds -o wide
kubectl describe mrap <activation-policy-name>
Cause: MRDs might not activate or activation policy doesn't match.
<!-- vale Google.Colons = YES -->Solution: verify activation policy patterns match MRD names:
kubectl get mrds
kubectl get mrap -o yaml
When adding safe-start to existing providers:
Learn more about the user experience in [disabling unused managed resources]({{<ref "disabling-unused-managed-resources">}}).