Skip to content

Take ownership of kubectl-managed resources

This content is for the v0.1 version. Switch to the latest version for up-to-date documentation.

Use a one-time server-side apply takeover when an existing resource was applied with kubectl and should now be managed by Kix.

This guide uses a Deployment as the example. Back up the live resource first, and perform the takeover during a normal change window.

Save the resource before changing its ownership:

❱ kubectl get deployment legacy-web -n takeover -o yaml --show-managed-fields > legacy-web.before.yaml

Keep any fields your workload relies on, especially immutable selectors, Service selectors, volume claims, and identity-bearing names. The managedFields section also shows which manager owns each part of the object.

Configure the package instance with the live Deployment’s selector and the state you want Kix to manage:

how-to/adoption/takeover-cluster.nix (L22–L36)
instances.takeover.legacy-web = {
package = webPackage;
# Keep the live Deployment's immutable selector.
selectorLabels = {
app = "legacy-web";
tier = "frontend";
};
config = {
replicas = 2;
environment = "production";
message = "This workload is managed by Kix";
};
};

View source on GitHub ↗

The example retains the existing app and tier selector. Its desired replica count is two, while the kubectl-managed fixture has one replica.

Check the cluster before contacting Kubernetes:

Run in kix-examples/
❱ kix check how-to-adoption-takeover
 TOOL         RESULT  DETAILS                                                       
 eval         pass    10 manifests evaluated                                        
 kubeconform  pass    skipped (this validation tool is not yet integrated with Kix) 
 pluto        pass    skipped (this validation tool is not yet integrated with Kix) 
 kyverno      pass    skipped (this validation tool is not yet integrated with Kix) 
 scorecard    pass    0 errors, 6 warnings, 2 info

Inspect the rendered Deployment:

Run in kix-examples/ Output excerpt
❱ kix build how-to-adoption-takeover --output json
{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "legacy-web",
    "namespace": "takeover"
  },
  "spec": {
    "replicas": 2,
    "selector": {
      "matchLabels": {
        "app": "legacy-web",
        "tier": "frontend"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "legacy-web",
          "tier": "frontend"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "nginx",
            "image": "docker.io/library/nginx:1.27-alpine"
          }
        ]
      }
    }
  }
}

Compare it with legacy-web.before.yaml. Do not continue if the rendered resource changes an immutable selector or omits a field the workload still needs. Correct the package configuration first.

Run an API-server dry run without taking ownership:

Run in kix-examples/ Output excerpt
❱ kix deploy how-to-adoption-takeover --dry-run -y Show output
⚠ Deployment/legacy-web@takeover field-ownership conflict on Deployment/legacy-web@takeover: another field manager owns one or more fields kix is trying to set. Re-run with --force-conflicts to take ownership. Underlying error: ApiError: Apply failed with 1 conflict: conflict with "kubectl": .spec.replicas: Conflict (ErrorResponse { status: "Failure", message: "Apply failed with 1 conflict: conflict with \"kubectl\": .spec.replicas", reason: "Conflict", code: 409 })
✗ halt-on-first-failure: field-ownership conflict on Deployment/legacy-web@takeover: another field manager owns one or more fields kix is trying to set. Re-run with --force-conflicts to take ownership. Underlying error: ApiError: Apply failed with 1 conflict: conflict with "kubectl": .spec.replicas: Conflict (ErrorResponse { status: "Failure", message: "Apply failed with 1 conflict: conflict with \"kubectl\": .spec.replicas", reason: "Conflict", code: 409 })
Dry run complete: 3 created, 2 configured, 0 unchanged, 1 failed, 4 cancelled
(exit code: 1)

Kix discovers live state through its management labels, so kix diff does not compare a resource that Kix has not managed before. The deployment dry run sends the intended manifests to the API server without persisting them.

The API server rejects a change when another field manager owns a field Kix is trying to change. The error identifies the resource and points to --force-conflicts.

By default, the run stops after the first conflict and ends with 1 failed, 4 cancelled. The cancelled resources have not been checked. Run the dry run again with --on-error continue to find conflicts on the remaining resources:

Run in kix-examples/
❱ kix deploy how-to-adoption-takeover --dry-run -y --on-error continue

This still skips resources that depend on a failed resource, but continues checking independent branches of the dependency graph. Review every reported field against the saved resource before using --force-conflicts.

When the diff and conflicting fields are expected, deploy with --force-conflicts:

Run in kix-examples/ Output excerpt
❱ kix deploy how-to-adoption-takeover --force-conflicts -y
~ Deployment/legacy-web@takeover configured
✔ Deployment/legacy-web@takeover ready
• activation 'how-to-adoption-takeover-nphkg40wdk30' → Active
Deploy complete: 6 created, 4 configured, 0 unchanged, 0 failed

The flag tells server-side apply to transfer conflicting fields in the Kix manifest to the kix field manager. Fields that Kix does not specify remain with their existing managers.

Check the result and save its field ownership:

Run in kix-examples/
❱ kix status how-to-adoption-takeover
❱ kubectl get deployment legacy-web -n takeover -o yaml --show-managed-fields > legacy-web.after.yaml

Use ordinary kix deploy how-to-adoption-takeover commands after the takeover. Do not leave --force-conflicts enabled for routine deployments, because a later conflict may represent another controller making an intentional change.