convoy docs
Search

Apply and prune

How convoy checks, stores and applies a render, and how it deletes the objects that a render no longer contains.

The sync is the last part of a reconcile. In the sync, convoy sends the render to the target cluster with Server-Side Apply. Then it deletes the objects that it applied before but that the new render does not contain. This deletion is the prune. convoy records the applied objects in the ReleaseInventory of the Release.

Inventory and prune Pick a scenario and change the prune policy. The table shows the result of one sync.
ObjectIn the previous inventoryIn the new renderResult
Deployment/podinfoYesYesKept applied again
ConfigMap/podinfo-v2NoYesApplied added to the inventory
ConfigMap/podinfo-v1YesNoPruned deleted from the cluster
Namespace/podinfo-oldYesNoProtected stays on the cluster, cleared from the inventory

The table shows spec.syncPolicy.prune.orphaned: true and includeNamespaces: false.

Steps of a sync#

convoy does these steps in this order. If one of the steps 1 to 6 fails or waits, the sync stops and the steps after it do not run. A failed postSync hook does not change the result of the sync.

  1. Dry run of the full render.
  2. Write the render to the render store, if a render store is configured.
  3. Run the preSync lifecycle hooks.
  4. Apply the render, one sync wave after the other.
  5. Prune the objects that the render no longer contains.
  6. Write the exact set of applied objects to the ReleaseInventory.
  7. Run the postSync lifecycle hooks.
  8. Record the render in the history and set status.lastSuccessful.

In manual sync mode, the sync runs only after a /sync request. See Sync policy.

Pre-apply dry run#

Before convoy stores or applies a render, it sends all objects of the render to the target cluster as a server-side apply dry run. The target cluster runs its schema checks, admission and RBAC on each object, but does not keep a change.

  • If the cluster rejects an object, convoy applies nothing. The Applied condition is False with reason Unappliable.
  • An Unappliable render is not a transient error. convoy does not retry with backoff. It checks the render again at the next periodic reconcile.
  • The dry run skips an object whose kind the cluster does not know yet. For example, a custom resource whose CRD is in an earlier sync wave.
  • The dry run does not use sync waves. It sends all objects in one pass.
  • A forced sync (/sync?force=true) skips the dry run. The real apply still fails if the cluster rejects an object.

The target cluster authorizes a dry run as a real write. The identity of the Cluster needs the same permissions for the dry run as for the apply.

Store the render#

If the controller has a render store (--render-store-url), convoy writes the render to it before the apply. If this write fails, convoy applies nothing. The Applied condition is False with reason DeployFailed, and convoy retries. Without a render store, convoy skips this step. See Pin and history.

Apply#

convoy applies each object with Server-Side Apply:

  • The field manager is convoy.
  • convoy sets force. If another field manager owns a field that the render sets, convoy takes the ownership of that field.
  • convoy applies a namespaced object without a namespace in the default namespace.

convoy applies the sync waves in ascending order. It applies all objects of a wave before it starts the next wave. Inside a wave, convoy keeps the order of the render. It does not sort the objects by kind. A wave with a health strategy waits until the previous wave is healthy. See Sync waves.

If the apply of an object fails, the sync stops. The Applied condition is False with reason DeployFailed, and convoy retries.

Inventory#

The ReleaseInventory has the same name and namespace as its Release. It records each applied object by group, kind, namespace and name. The API version is not part of the identity. See ReleaseInventory.

convoy writes the inventory two times in a sync:

  • After each wave. The inventory is the union of the previous inventory and the objects applied so far. A sync that stops at a wave keeps the records of the previous render. A later prune can then still find them.
  • After the prune. The inventory is the exact set of objects that this sync applied. The inventory no longer has the records of the previous render that the prune handled.

Prune#

The prune runs only when spec.syncPolicy.prune.orphaned is true. It runs only after all waves applied without an error. A dry-run rejection, an apply error or a wave that waits stops the sync before the prune. So convoy never prunes on a partial or failed sync.

The prune deletes each object that is in the inventory but not in the applied set. Two kinds are protected:

KindDeleted only when
CustomResourceDefinition (apiextensions.k8s.io)prune.includeCRDs is true
Namespace (core group)prune.includeNamespaces is true

A protected object stays on the cluster. convoy clears it from the inventory, so convoy does not manage it after this sync.

An object that is already absent does not cause an error. If the delete of an object fails, the Applied condition is False with reason PruneFailed. The inventory keeps the records of the previous render, so convoy tries the prune again at the next retry.

A rollback to the last healthy render also prunes, with the same rules. See Progressive rollout.

Status#

  • status.lastAttempted records each sync attempt, also a failed one.
  • status.lastSuccessful changes only when convoy applied a render. A failed dry run, apply or prune does not change it. A manual-mode reconcile that applies nothing does not change it.
  • status.waves records the progress of each sync wave.

See Status and conditions.

In the code

convoy v0.9.0