Explanation
Server-side apply, field ownership, and drift
How Kubernetes field managers let Kix distinguish desired fields from controller-owned state, and why conflicts require an ownership decision.
Kix applies resources with Kubernetes server-side apply and the field manager
name kix. The API server records which manager owns each field in
metadata.managedFields. That record lets Kix coexist with controllers that
change the same object without treating every controller-written value as
part of Kix’s desired state.
This page is hand-maintained. Check kix/lib/core/resource.nix and
cli/kix/src/cluster/apply.rs when in doubt.
Ownership follows fields, not objects
Section titled “Ownership follows fields, not objects”Several managers can own different parts of one Kubernetes object. Kix might
own a Deployment’s container image, labels, and resource limits while an HPA
owns spec.replicas. Kubernetes does not require one tool to own the whole
Deployment.
Server-side apply compares the fields a manager sends with the ownership record. When Kix tries to change a field another manager owns, the API server returns a conflict instead of choosing a winner. This is useful evidence: the desired manifest and the live controller disagree about responsibility for a specific field.
Fields that Kix never sent are outside its field set. Status, API defaults, and values added by controllers therefore do not conflict merely because they appear on the live object.
A conflict has two valid resolutions
Section titled “A conflict has two valid resolutions”The resolution depends on which manager should control the field.
If Kix should own it, stop or reconfigure the other writer, then use
--force-conflicts for the ownership transfer. A forced server-side apply
takes the conflicting fields for Kix and writes the values in the manifest.
Leaving that flag on every deploy is risky because a new, unrelated ownership
dispute will also be forced.
If the other manager should own it, the package should stop sending the field.
Some resources cannot omit a field on creation, and some packages carry a
complete upstream manifest. For those cases, the package can declare the
field under ownership.cede. Kix sends it when creating the object. On a
later conflict confined to the declared paths, Kix retries once without those
fields, allowing the other manager to retain them.
This makes ceding a narrow package contract. The package author knows that a
particular controller is expected to take spec.replicas or a CRD’s
conversion fields. A conflict from an unexpected manager or on an undeclared
path still stops the deployment. Automatically forcing or discarding every
conflict would erase that distinction.
Drift uses the same ownership boundary
Section titled “Drift uses the same ownership boundary”After an apply, Kix hashes the live fields that its field manager owns and
stores the result in kix.run/applied-hash. kix drift later selects the
same owned fields, hashes their current values, and compares the result with
the stamp.
This means drift is narrower than a whole-object comparison:
- a controller updating
statusis not drift when Kix does not own status; - an API server default is not drift when Kix never sent the field;
- changing a field Kix owns is drift, even when the change came through a
subresource such as
/scale; - a field Kix successfully ceded falls outside Kix’s owned field set and is no longer judged as Kix drift.
kix diff applies the same boundary when it compares a build with the live
cluster. It extracts Kix-owned fields from live objects so defaults and
controller state do not fill the report with irrelevant differences.
Field ownership answers who is responsible for a value. Readiness answers whether the resulting object is usable. A resource can have no ownership conflict and still fail its readiness check, so deployment handles the two separately.