ServerSideApply¶
There are 2 ways to apply manifests in kubernetes. Client-Side and Server-Side
Client-Side Apply in kubernetes¶
By default kubectl apply uses the traditional client-side way.
The full desired state is stored in the kubectl.kubernetes.io/last-applied-configuration annotation on the object. ArgoCD uses this annotation to compute diffs and detect drift. This approach has a hard limit: annotations cannot exceed 262144 bytes (256KB). Large CRDs with complex specs will hit this limit and fail to apply.
Server-Side Apply in kubernetes¶
It became GA in kubernetes 1.22 as a new object merge algorithm, as well as tracking of field ownership, running on the Kubernetes API server.
Server-Side Apply allows multiple "managers" (e.g., ArgoCD, kubectl, other controllers) to declaratively manage different parts of a resource's configuration. Each manager owns specific fields in the resource's manifest. The Kubernetes API server tracks which manager owns which fields in the resource's spec. This is called field ownership. When a manager applies changes, only the fields it owns are updated, leaving fields owned by other managers untouched. If two managers attempt to modify the same field, the API server detects the conflict and rejects the change unless explicitly forced.
To see what "manager" controls what fields we can use this:
kubectl get RESOURCE RESOURCENAME --show-managed-fields -o yaml
ServerSideApply in argocd¶
- Resource exceeds the 262144 bytes annotation limit (common with large CRDs).
- Patching resources not fully managed by ArgoCD.
- More declarative field-ownership tracking instead of last-applied-state tracking.
- Improved merge behavior: changes by other controllers (e.g. HPA) are not overwritten by ArgoCD unless it owns those fields.
- Conflict detection: ArgoCD reports conflicts when another manager modifies fields it is also trying to manage.
- Declarative ownership: explicit per-field responsibility makes multi-tool management auditable.
- CRD compatibility: required for certain advanced controllers that rely on field ownership.
- Conflict risk: multiple tools modifying the same fields can cause conflicts if not carefully managed.
- API server load shift: merging moves from ArgoCD to the API server.
Enable ServerSideApply in argocd¶
At Application controller level¶
We can enable server side apply with the param --server-side-diff-enabled in the argocd-application-controller, or with the following setting in the argocd-cmd-params-cm configMap
controller.diff.server.side: true # false by default
Enables the server side diff feature at the application controller level. Diff calculation will be done by running a server side apply dryrun (when diff cache is unavailable)
At Application level¶
SSA can be enabled per ArgoCD Application using syncOptions:
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
syncPolicy:
syncOptions:
- ServerSideApply=true
At resource level¶
The most surgical approach: annotate only the specific resources that need SSA, leaving all others on client-side apply. This is the recommended pattern when SSA is needed only because of large CRDs hitting the annotation limit.
Note — force-conflicts is automatic: ArgoCD unconditionally runs
kubectl apply --server-side --force-conflictswhenever SSA is enabled, whether at Application level or via resource annotation. Field ownership conflicts from previous client-side apply managers are resolved automatically on the first SSA sync — no manualkubectlintervention needed.
metadata:
annotations:
argocd.argoproj.io/sync-options: ServerSideApply=true
To apply SSA only to CRDs when deploying manifests that include them (e.g. via kustomize), use a patch so the rest of the resources continue using client-side apply:
patches:
- patch: |-
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: placeholder
annotations:
argocd.argoproj.io/sync-options: ServerSideApply=true
target:
kind: CustomResourceDefinition
Diff noise on non-declared fields¶
Enabling SSA — whether globally or per resource — can surface diffs on fields that were never declared in Git.
Root cause¶
With SSA, the Kubernetes API server or mutating admission webhooks may inject default values into fields not declared in the manifest (e.g. weight: 1 on a single-backend Istio route). These fields are owned by a different field manager (the webhook or the API server), not by ArgoCD. ArgoCD sees the live object has a value that is absent from the desired state and reports drift. This was less visible with client-side apply because the diff was based solely on the last-applied annotation, which only contained what ArgoCD had explicitly set.
Solutions¶
Option 1 — Declare the field explicitly (cleanest)¶
Add the defaulted value to your manifest so the desired and live states match:
weight: 1
Option 2 — ignoreDifferences with a JSON pointer¶
spec:
ignoreDifferences:
- group: networking.istio.io
kind: VirtualService
jsonPointers:
- /spec/http/*/route/*/weight
Option 3 — ignoreDifferences scoped to a specific field manager (ArgoCD 2.7+)
spec:
ignoreDifferences:
- group: networking.istio.io
kind: VirtualService
managedFieldsManagers:
- istio-pilot
Typical progression when adopting SSA for large CRDs¶
Large CRD hits the 262144 annotation limit
↓
Enable SSA only on the affected resources (annotation or kustomize patch)
↓
Gain: large CRDs apply successfully
Loss: diff noise appears on fields defaulted by webhooks or the API server
↓
Add ignoreDifferences rules or declare the fields explicitly to compensate