Sync policy
When convoy applies a render, how a /sync request works, and when convoy reconciles a Release again.
The sync policy tells convoy when to apply a render. In automatic mode, convoy applies the render
at each reconcile. In manual mode, convoy renders and reports, but it applies only after a
/sync request. The sync policy also sets how often convoy reconciles the Release, and how it
retries after a failure.
The fields are in spec.syncPolicy. See Release.
Automatic and manual mode#
spec.syncPolicy.automatic selects the mode. The field has no default value. If you do not set
it, the value is false, and the Release uses manual mode.
Automatic (automatic: true) | Manual (automatic: false) | |
|---|---|---|
| Fetch, render and mutations | At each reconcile | At each reconcile |
| Apply | At each reconcile | Only when a /sync request is pending |
| Drift correction | Yes. The observer requests a /sync when it finds drift. | No. The observer only reports drift. |
In automatic mode, convoy applies the full render at each reconcile, also when nothing changed. This also corrects drift at each periodic reconcile.
In manual mode without a pending /sync request, convoy compares the current render with
status.lastSuccessful.renderRef:
- Equal. The
Appliedcondition isTruewith reasonApplied. The message says that the Release is in sync and that the apply waits for/sync. - Different, or no render applied yet. The
Appliedcondition isFalsewith reasonWaitingForSync.
In both cases convoy applies nothing and does not run the pre-apply dry run.
The /sync request#
POST releases/<name>/sync writes the current time to the annotation
convoy.kumodo.io/sync-requested-at on the Release. A change to this annotation starts a
reconcile.
In manual mode, a request is pending when the annotation time is later than
status.lastHandledSyncAt. convoy compares the times to the second.
- convoy sets
status.lastHandledSyncAtonly after a successful apply. - If the apply fails or waits, the request stays pending. The next reconcile tries the apply again.
- In automatic mode, convoy does not use
status.lastHandledSyncAt. A/syncrequest only starts a reconcile.
These writes also set convoy.kumodo.io/sync-requested-at. In manual mode, each one counts as a
/sync request:
- a write to the
/pin,/mutationsor/lifecyclesubresource - a
/syncrequest from the observer health loop. The observer sends one when a sync wave completes, when health assessment reaches a new render, or when a batch of lifecycle hooks completes.
kubectl convoy release sync podinfoForced sync#
POST releases/<name>/sync?force=true also writes convoy.kumodo.io/sync-force-requested-at.
The next reconcile then:
- skips the pre-apply dry run. See Apply and prune.
- starts a new
Progressingrollout, if the Release has afailurePolicy. This clears aFailedorRolledBackrollout. See Progressive rollout.
A force request has effect for one reconcile only. convoy writes status.lastHandledForceSyncAt
after that reconcile, also when the reconcile fails or waits.
kubectl convoy release sync podinfo --forceWhat starts a reconcile#
The release controller reconciles a Release when one of these events occurs:
- The Release is created.
metadata.generationchanges. Only a change tospecchanges the generation.- The annotation
convoy.kumodo.io/sync-requested-atchanges. - The informer resync occurs. The default period is 2 minutes (flag
--resync). - The time of a scheduled requeue arrives. The requeue rules are below.
- The observer writes new health data to the
ReleaseInventory. - The status of the
AppliedorHealthycondition changes on a Release of a lower cluster wave on the sameCluster.
A status write on a Release does not start a reconcile of that Release. The write does not change the generation or the annotation. So the status writes of the controller and the observer do not cause a loop.
A change to mutations or lifecycle through the main resource does not change the generation.
convoy uses the change at the next periodic reconcile. A write through the /mutations or
/lifecycle subresource starts a reconcile at once.
Requeue rules#
After each reconcile, convoy decides when to reconcile the Release again.
| Result of the reconcile | Next reconcile |
|---|---|
Transient failure, retryBackoff.initial is set | After the retryBackoff delay |
Transient failure, retryBackoff.initial is not set | After the delay of the default rate limiter |
| Success, or a hold that is not an error | After spec.syncPolicy.interval |
Rollout in Progressing | After interval, or after 10 seconds if that is shorter |
Transient failures include SourceNotReady, ValuesNotReady, ClusterNotReady, DeployFailed
and PruneFailed. Unsupported, Unappliable, Pinned, HookFailed and the waiting reasons
are not transient. convoy checks them again at the next periodic reconcile.
interval#
spec.syncPolicy.interval is the period of the periodic reconcile. The observer uses the same
interval for its drift checks. See Drift.
The field has no default value. With interval not set or 0, convoy schedules no periodic
reconcile. Only the informer resync and the events above start a reconcile.
retryBackoff#
With retryBackoff.initial set, convoy calculates the delay before retry number n as
initial × factor^(n-1):
- If
factoris less than 1, convoy uses 2. - The delay is not more than
max. Ifmaxis not set, the limit is 1000 seconds. - convoy counts the failures in memory. A success or a hold resets the count. A restart of the controller also resets it.
Without retryBackoff.initial, the work queue retries with its default rate limiter. That
limiter starts at 5 milliseconds and doubles the delay up to 1000 seconds.
In the code
convoy v0.9.0
internal/controller/release.(*Controller).syncHandlerinternal/controller/release/controller.go:199internal/controller/release.(*Controller).stageGatesinternal/controller/release/controller.go:1027internal/controller/release.pendingSyncinternal/controller/release/controller.go:341internal/controller/release.pendingForceSyncinternal/controller/release/controller.go:362internal/controller/release.requeueAfterinternal/controller/release/controller.go:325internal/controller/release.backoffDelayinternal/controller/release/controller.go:692internal/registry/core/release.(*SyncREST).Connectinternal/registry/core/release/store.go:338internal/controller/base.(*Controller).shouldEnqueueOnUpdateinternal/controller/base/controller.go:159