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.
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.
- Dry run of the full render.
- Write the render to the render store, if a render store is configured.
- Run the
preSynclifecycle hooks. - Apply the render, one sync wave after the other.
- Prune the objects that the render no longer contains.
- Write the exact set of applied objects to the
ReleaseInventory. - Run the
postSynclifecycle hooks. - 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
Appliedcondition isFalsewith reasonUnappliable. - An
Unappliablerender 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
defaultnamespace.
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:
| Kind | Deleted 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.lastAttemptedrecords each sync attempt, also a failed one.status.lastSuccessfulchanges 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.wavesrecords the progress of each sync wave.
In the code
convoy v0.9.0
internal/controller/release.(*Controller).stageSyncinternal/controller/release/controller.go:1092internal/controller/release.(*Controller).dryRunGateinternal/controller/release/controller.go:1078internal/controller/release.(*Controller).persistRenderinternal/controller/release/controller.go:1775internal/controller/release.applyinternal/controller/release/deployer.go:138internal/controller/release.(*Controller).pruneinternal/controller/release/controller.go:1901internal/controller/release.protectedFilterinternal/controller/release/controller.go:1918internal/controller/release.unionByIDinternal/controller/release/controller.go:1945