Revisions

stateDiagram-v2
direction LR
  state "Revision 1" as rev1
  state "Revision 2" as rev2
  state "Revision 3" as rev3
  [*] --> rev1
  rev1 --> rev2
  rev2 --> rev3
  rev3 --> [*]

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

stateDiagram-v2
direction LR
  state "Not Ready" as not_ready
  [*] --> Pending
  Pending --> Available
  Available --> not_ready
  Pending --> not_ready
  not_ready --> Available
  Available --> Archived
  not_ready --> Archived
  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

flowchart LR
  subgraph Revision 1
    deploy1["Deployment <b>my-deployment</b><br>image: my-image:rev1"]
  end
  subgraph Revision 2
    deploy2["Deployment <b>my-deployment</b><br>image: my-image:rev2"]
  end
  deploy1--patched object-->deploy2

“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

flowchart LR
  subgraph Revision 1
    deploy1["Deployment <b>my-deployment-v1</b><br>image: my-image:rev1"]
  end
  subgraph Revision 2
    deploy2["Deployment <b>my-deployment-v2</b><br>image: my-image:rev2"]
  end
  deploy1-. indirect successor .->deploy2

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

flowchart LR
  subgraph rev1[Revision 1]
    cm1["ConfigMap <b>my-config</b><br>data: {key: value-v1}"]
    deploy1["Deployment <b>my-deployment-v1</b><br>image: my-image:rev1"]
  end
  subgraph rev2[Revision 2]
    cm2["ConfigMap <b>my-config</b><br>data: {key: value-v2}"]
    deploy2["Deployment <b>my-deployment-v2</b><br>image: my-image:does-not-exist"]
  end
  subgraph rev3[Revision 3]
    cm3["ConfigMap <b>my-config</b><br>data: {key: value-v3}"]
    deploy3["Deployment <b>my-deployment-v3</b><br>image: my-image:rev3"]
  end
  style rev2 fill:#ffbabd,stroke:#9f7b7d
  deploy1-->deploy2
  deploy2-->deploy3
  cm1-.->cm2
  cm2-.->cm3

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
sequenceDiagram
autonumber
  participant v1 as ObjectSet v1 Reconciler
  participant api as kube-apiserver
  participant v2 as ObjectSet v2 Reconciler

  loop Reconciliation
    v1->>api: reconcile/update child-1 .spec
    v1->>api: reconcile/update child-2 .spec
  end

  Note over v2: phase-1 starts
  v2->>api: Take ownership of child-1 and<br> reconcile/update .spec
  par ObjectSet v1 Reconciler
    api-)v1: child-1 Update event
    activate v1
    v1->>api: reconcile/update child-2 .spec
    deactivate v1
  and ObjectSet v2 Reconciler
    api-)v2: child-1 Update event
    activate v2
    v2->>api: reconcile/update child-1 .spec
    deactivate v2
  end

  Note over v1,v2: Until phase-1 completes
  loop Reconciliation
    v2->>api: reconcile/update child-1 .spec
    v1->>api: reconcile/update child-2 .spec
  end

  Note over v2: phase-2 starts
  v2->>api: reconcile/update child-1 .spec
  activate v2
  v2->>api: Take ownership of child-2 and<br> reconcile/update .spec
  par ObjectSet v1 Reconciler
    api-)+v1: child-1 Update event
    deactivate v1
  and ObjectSet v2 Reconciler
    api-)v2: child-1 Update event
    activate v2
    v2->>api: reconcile/update child-1 .spec
    v2->>api: reconcile/update child-2 .spec
    deactivate v2
  end