Revisions

Revision 1

Revision 2

Revision 3

Revisions are iterations and changes of a deployment over time. To support zero-downtime deployments, even if something goes wrong, Package Operator can manage multiple active revisions at the same time. This strategy is also often referred to as “A/B deployment” or “canary deployment”.

A revision is represented by the ObjectSet/ClusterObjectSet APIs.

While revision are ordered on a time axis, Package Operator makes no assumptions on the contents of each revisions. This means that Revision 3 could contain the same spec as Revision 1, rolling back changes introduced by Revision 2.

Revision Lifecycle

Not Ready

Pending

Available

Archived

  • Pending
    Intermediate state before the controller posted its first update.
  • Available
    All availability probes are successful.
  • Not Ready
    One or more availability probes are unsuccessful.
  • Archived
    (Cluster)ObjectSet is shutdown and only acts as a revision tombstone for rollbacks.

In addition to these major lifecycle states, (Cluster)ObjectSets may be Paused, stopping reconciliation, while still reporting status. This can be useful for testing and debugging.

Rollout Handling

Depending on the contents of the new revision, objects are eventually either:

  • Patched
    If the object still part of the new revision, it will be handed over to the next revision.
  • Deleted
    If the object is not part of the new revision, it will be deleted when the old revision is archived.

In-Place Updates

Revision 2

Revision 1

patched object

Deployment my-deployment
image: my-image:rev1

Deployment my-deployment
image: my-image:rev2

“In-Place” updates happen when a new revision contains an object with the same name and type as the previous revision. Objects are handed over to a new revision and patched as needed.

When all objects have been handed over to a new revision, the previous revision is automatically archived.

A/B Updates

Revision 2

Revision 1

indirect successor

Deployment my-deployment-v1
image: my-image:rev1

Deployment my-deployment-v2
image: my-image:rev2

A/B updates happen when a new revision does not contain an object with the same name and type as a previous revision. A new object is created in the new revision without affecting the old object.

The old revision is only archived when the new revision has completely finished its rollout and is “Available”.

Intermediate (Failed) Revisions

Revision 3

Revision 2

Revision 1

ConfigMap my-config
data: {key: value-v1}

Deployment my-deployment-v1
image: my-image:rev1

ConfigMap my-config
data: {key: value-v2}

Deployment my-deployment-v2
image: my-image:does-not-exist

ConfigMap my-config
data: {key: value-v3}

Deployment my-deployment-v3
image: my-image:rev3

Under normal circumstances at max 2 Revisions can be active during rollout. An old and a new revision.

If a revision fails to become available due to e.g. misconfiguration and a new revision supersedes it, multiple intermediate revisions might be active until the latest revision becomes available.

Intermediate revisions will only be cleaned up if:

  • Latest revision becomes available
  • Revision is not reconciling any objects anymore
  • Latest revision is not containing any object still actively reconciled by an intermediate

In the example above, the ConfigMap “my-config” is handed over from revision 1 to revision 2 and in the end to revision 3.

As soon as revision 3 takes ownership of the ConfigMap, the failed intermediate revision 2 can be archived, as “my-deployment-v2” no longer exists in revision 3 and is thus safe to delete.

Internals

ObjectSet to ObjectSet Handover

apiVersion: package-operator.run/v1alpha1
kind: ObjectSet
metadata:
  name: v1
spec:
  phases:
  - name: phase-1
    objects: [{name: child-1}]
  - name: phase-2
    objects: [{name: child-2}]
---
apiVersion: package-operator.run/v1alpha1
kind: ObjectSet
metadata:
  name: v2
spec:
  phases:
  - name: phase-1
    objects: [{name: child-1}]
  - name: phase-2
    objects: [{name: child-2}]
  previous:
  - name: v1
    kind: ObjectSet
    group: package-operator.run
ObjectSet v2 Reconcilerkube-apiserverObjectSet v1 ReconcilerObjectSet v2 Reconcilerkube-apiserverObjectSet v1 Reconcilerloop[Reconciliation]phase-1 startspar[ObjectSet v1 Reconciler][ObjectSet v2 Reconciler]Until phase-1 completesloop[Reconciliation]phase-2 startspar[ObjectSet v1 Reconciler][ObjectSet v2 Reconciler]reconcile/update child-1 .spec1reconcile/update child-2 .spec2Take ownership of child-1 and reconcile/update .spec3child-1 Update event4reconcile/update child-2 .spec5child-1 Update event6reconcile/update child-1 .spec7reconcile/update child-1 .spec8reconcile/update child-2 .spec9reconcile/update child-1 .spec10Take ownership of child-2 and reconcile/update .spec11child-1 Update event12child-1 Update event13reconcile/update child-1 .spec14reconcile/update child-2 .spec15