The architecture of Autopilot Operators is determined by Autopilot and enforced in generated code. While generated code can be manually modified to customize this architecture, it is not currently recommended.
To better understand the architecture of an Autopilot Operator, let’s use the Example Canary Operator as an example.
Consider the following diagram of our Canary. The Canary Operator has the following architecture:
is in Initializing phase|s[Canary Scheduler] w2[Waiting
is in Waiting phase|s w3[Evaluating
is in Evaluating phase|s w4[Promoting
is in Promoting phase|s w5[Rollback
is in Rollback phase|s s-->|Scheduler is called
by Manager on
CRD change or input output event|m[Manager] spec[Canary API Spec]-->|Watch Canary API Types|c[Kubernetes API Client] c-->|communcation with Kubernetes API|m end classDef userDefined fill:#0DDF00,stroke:#233,stroke-width:4px; class w1,w2,w3,w4,w5,spec,userDefined userDefined; classDef generated fill:#fae100,stroke:#233,stroke-width:4px; class s,generated generated; classDef k8s fill:#eb7b26,stroke:#233,stroke-width:4px; class m,c,k8s k8s;
For reference, code for each component can be found in the following table:
|Initializing Worker||initializing/worker.go||User Project (user-defined)|
|Waiting Worker||waiting/worker.go||User Project (user-defined)|
|Evaluating Worker||evaluating/worker.go||User Project (user-defined)|
|Promoting Worker||promoting/worker.go||User Project (user-defined)|
|Rollback Worker||rollback/worker.go||User Project (user-defined)|
|Canary Scheduler||scheduler/sceduler.go||User Project (generated)|
|Manager||manager.go||Imported from controller-runtime|
|K8s Client||client/interfaces.go||Imported from controller-runtime|
Manager connects our operator to the underlying Kubernetes watches that trigger the operator to resync on a change.
Manager calls into our generated
Scheduler each time a top-level Custom Resource is created, updated, or deleted. A custom scheduler, generated for each Operator, then executes a resync function each time it is called by the operator.
The main control loop of the application looks like the following:
from Cache] retrieve-->eventType[Handle Event Type] eventType-->|create/update event|phase[Determine Canary Phase] eventType-->|delete event|finalizer[Call Optional User-defined Finalizer] phase-->inputs[Read inputs for Phase] inputs-->worker[Construct Worker corresponding to Phase] worker-->sync[Call worker.Sync function with inputs] sync-->|return error|backoff[Retry with Backoff] sync-->|return outputs|ensure[Ensure all outputs are written to cluster] sync-->|return next phase|next[update the Canary with the new Phase] next-->m end classDef userDefined fill:#0DDF00,stroke:#233,stroke-width:4px; class sync userDefined; classDef generated fill:#fae100,stroke:#233,stroke-width:4px; class s,receive,retrieve,eventType,phase,finalizer,inputs,worker,backoff,ensure,next generated; classDef k8s fill:#eb7b26,stroke:#233,stroke-width:4px; class m k8s;
The main control loop is started by Autopilot’s
Users are expected to modify and update their
worker.go files. When the
autopilot.yaml file changes,
the project should be regenerated.
worker.go files will not be overwritten by a regenerate, allowing users to iteratively regenerate the scheduler for their operator. Users can then update their workers as necessary.