(Alpha) Server-Side Apply
The --ssa flag scaffolds APIs with Server-Side Apply support, enabling safer field management when multiple actors modify the same resources.
This adds:
- API markers (
+genclient,+kubebuilder:ac:generate=true,+kubebuilder:resource:path=<plural>) for ApplyConfiguration generation - Makefile integration to generate type-safe ApplyConfiguration types alongside CRD and RBAC manifests
When to use it
Use Server-Side Apply when:
- Multiple controllers or users manage the same resource
- Users customize CRs with labels, annotations, or spec fields your controller shouldn’t overwrite
- You want declarative field ownership tracking
- Other operators will manage instances of your CRs (they can import your generated ApplyConfiguration types)
How it works
Traditional Update() overwrites the entire object. Server-Side Apply with the client’s Apply method only manages the fields you specify.
After running make manifests, ApplyConfiguration types are created at:
api/<version>/applyconfiguration/api/<version>/ (single group)
api/<group>/<version>/applyconfiguration/<group>/<version>/ (multi-group)
Import them as (multi-group projects use
example.com/myproject/api/<group>/v1/applyconfiguration/<group>/v1):
appsv1apply "example.com/myproject/api/v1/applyconfiguration/api/v1"
Mixing SSA and non-SSA APIs
The +kubebuilder:ac:generate=true marker is package-level: it enables ApplyConfiguration generation for all kinds in the same group/version. Kinds scaffolded without --ssa include the +kubebuilder:ac:generate=false marker, so ApplyConfiguration types are only generated for the kinds that opted in. When you create an API with --ssa, kinds previously scaffolded without this marker in that group/version receive it automatically.
To change which kinds are generated:
- Set
+kubebuilder:ac:generate=trueabove a kind to include it - Set
+kubebuilder:ac:generate=falseabove a kind to exclude it
Usage
Create an API with the --ssa flag:
kubebuilder create api --group apps --version v1 --kind Application --ssa
Implement Server-Side Apply in your controller:
import (
"context"
metav1apply "k8s.io/client-go/applyconfigurations/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
appsv1apply "example.com/myproject/api/v1/applyconfiguration/api/v1"
)
func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Build desired state - specify only fields you manage
app := appsv1apply.Application(req.Name, req.Namespace).
WithStatus(appsv1apply.ApplicationStatus().
WithConditions(metav1apply.Condition().
WithType("Available").
WithStatus("True").
WithReason("Reconciled")))
// Apply status, forcing ownership of the fields this controller manages
if err := r.SubResource("status").Apply(ctx, app,
client.FieldOwner("application-controller"), client.ForceOwnership); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
Then run:
make generate manifests
Best practices
- Always specify
client.FieldOwner("controller-name")to identify your controller - Controllers should pass
client.ForceOwnershipfor the fields they manage; Kubernetes recommends that controllers always force conflicts - Only specify fields your controller manages using the builder pattern