propeller logo
k8s

Features

Custom Resource lifecycle state machines, spec and status references, and the proplet scheduler for the Propeller Kubernetes Operator.

Proplet

A Proplet resource represents one worker node. The spec.type field selects between two fundamentally different modes of operation.

Types

k8s — The operator creates and manages a Kubernetes Deployment in the same cluster. The pod runs the proplet image, which connects back to the same SuperMQ broker using the credentials in spec.connectionConfig. The operator reconciles the Deployment whenever the spec changes and reflects the Deployment's readiness in the Proplet status.

external — The proplet is a device or process running outside the cluster (a Raspberry Pi, an ESP32, a Docker container on another host). The operator does not create any Kubernetes resources for it. Instead, it watches the MQTT control/proplet/alive topic and transitions the Proplet phase to Running when heartbeats arrive within the configured threshold, and to Offline when they stop.

Proplet Lifecycle

Proplet Lifecycle State Machine

Figure: Proplet state machine showing transitions between Initializing, Running, and Offline phases. A proplet starts in Initializing, moves to Running when heartbeats are received (external) or when the Deployment is ready (k8s), and transitions to Offline when heartbeats stop. When heartbeats resume, the proplet returns to Running.

  • Initializing — default phase on creation; no heartbeats received yet, or k8s Deployment not yet ready.
  • Running — heartbeat received within --last-seen-threshold (default 30 s) for external proplets, or all Deployment replicas ready for k8s proplets.
  • Offline — heartbeat not received within threshold (external only).

Proplet Conditions

ConditionMeaning
ReadyProplet can accept tasks
ConnectedMQTT heartbeat is current (external) or pod is available (k8s)
HealthyProplet is processing tasks without errors

Proplet Spec Reference

FieldTypeRequiredDefaultDescription
typek8s \externalYes
k8s.imagestringYes (k8s)Container image for the proplet pod
k8s.logLeveldebug \info \warn \error
k8s.replicasint32No1Number of pod replicas (0–100)
k8s.pluginDirstringNoPath inside the container where WASM plugin files are loaded from (PROPLET_PLUGIN_DIR)
external.deviceTypestringNoDevice category label used for task matching (e.g. raspberry-pi-4)
external.capabilities[]stringNoCapability strings the device advertises (e.g. wasm, gpu)
resourcesResourceListNoKubernetes CPU and memory requests/limits applied to the proplet pod
connectionConfig.mqttAddressstringYesMQTT broker URL (e.g. tcp://mqtt:1883)
connectionConfig.domainIdstringYesSuperMQ domain ID
connectionConfig.channelIdstringYesSuperMQ channel ID
connectionConfig.clientIdstringYesSuperMQ client credential ID; must match proplet_id in alive messages for external proplets
connectionConfig.clientKeystringNo*SuperMQ client secret (inline). Exactly one of clientKey or clientKeySecretRef must be set
connectionConfig.clientKeySecretRefSecretKeySelectorNo*Reference to a Kubernetes Secret holding the client secret. Mutually exclusive with clientKey
connectionConfig.mqttQosuint8No0MQTT QoS level (0–2)
connectionConfig.mqttTimeoutdurationNo30sBroker operation timeout

Proplet Status Reference

FieldDescription
phaseInitializing, Running, or Offline
alivetrue while heartbeats are arriving within the --last-seen-threshold
conditions[Ready]True when the proplet is ready to accept tasks
conditions[Connected]True when MQTT heartbeat is current (external) or pod is available (k8s)
conditions[Healthy]True when the proplet is operating without errors
lastSeenTimestamp of the most recent alive heartbeat
aliveHistoryLast 10 heartbeat timestamps
taskCountTotal tasks assigned to this proplet
availableResourcesCPU and memory reported as available (from spec for k8s; from heartbeat for external)
k8sStatus.readyReplicasReady pod replicas (k8s type only)
k8sStatus.availableReplicasAvailable pod replicas (k8s type only)
metadataDevice metadata reported in alive messages: description, tags, location, ip, environment, os, hostname, cpuArch, totalMemoryBytes, propletVersion, wasmRuntime
latestMetricsMost recent metrics sample: cpuMilliPercent (CPU × 1000), memoryMilliPercent (memory × 1000), memoryBytes, timestamp

Task

A Task resource defines a single WASM workload. The TaskReconciler drives it through a phase machine, selecting a proplet, dispatching execution, and capturing the result.

Task Lifecycle

Task Lifecycle State Machine

Figure: Task state machine showing all valid transitions. Tasks begin in pending and move directly to running when dispatched to a proplet. From running, tasks can complete successfully (completed), fail (failed), or be stopped (interrupted). The operator enforces valid transitions and supports retry/restart flows from terminal states back to pending.

Valid state transitions are enforced by the operator. Task completion and failure arrive via control/proplet/results MQTT messages; the operator validates each proposed transition before applying it.

FromAllowed next states
pendingscheduled, running, completed, failed, skipped
scheduledrunning, completed, failed, skipped
runningcompleted, failed, interrupted
completedpending (for restart or recurring tasks)
failedpending (for retry)
interruptedpending (for resume)
skipped— (terminal)

Execution Paths

Task Execution Paths

Figure: Comparison of task execution paths for external vs k8s proplets. External proplets receive tasks via MQTT and return results through the broker. K8s proplets run as Kubernetes Jobs with ConfigMaps for task configuration and multiple result extraction methods (Job annotations, ConfigMaps, Secrets, or Pod annotations).

External proplet path:

  1. TaskReconciler picks up the new Task and resolves the target proplet by spec.propletSelector.propletId.
  2. The reconciler looks up the Proplet CR and confirms its type is external.
  3. It publishes an MQTT message to control/manager/start containing the full task spec: function name, WASM file or image URL, inputs, environment variables, scheduling parameters, monitoring profile, and the assigned proplet ID.
  4. The proplet receives the message, loads the WASM module with its runtime (e.g. Wasmtime), and executes the named function.
  5. The proplet publishes the result to control/proplet/results.
  6. The TaskReconciler's MQTT result handler receives the message, stores the output in status.results, and sets status.phase = completed.

k8s proplet path:

  1. TaskReconciler resolves the proplet and confirms its type is k8s.
  2. It creates a ConfigMap containing task environment variables (function name, inputs, monitoring settings, etc.) and an optional wasm_file_provided key if a WASM file is embedded.
  3. It creates a Kubernetes Job whose pod runs the image specified in spec.imageUrl.
  4. The reconciler is notified of Job changes via Kubernetes owner-reference watches (the Owns(&batchv1.Job{}) watch triggers reconcile on every Job status update). A safety requeue at 5 minutes handles edge cases where the watch event is missed. On Job completion it attempts multi-method result extraction: Job annotations, container terminated message, a result ConfigMap ({jobName}-result), a result Secret ({jobName}-result), or Pod annotations—whichever is populated first.
  5. On success it sets status.phase = completed and stores the extracted result in status.results.

Proplet Selection

The spec.propletSelector field controls which proplet receives the task. The scheduler evaluates candidates in two passes:

  1. Candidate filter — retains only proplets in Running phase that satisfy all selector criteria:

    • propletId: exact name match
    • matchLabels: all specified labels must be present on the Proplet CR
    • matchDeviceTypes: Proplet's external.deviceType must be in the list
    • matchCapabilities: all listed capabilities must appear in the Proplet's external.capabilities
    • preferredPropletType: filters by k8s or external if set; any accepts both
  2. Scoring and selection — the default scheduler is round-robin: the proplet after the last-selected one scores 0.1; all others score 1.0. The proplet with the lowest score is selected.

If no proplet satisfies the filter, the task remains in pending and is requeued.

Task Spec Reference

FieldTypeRequiredDefaultDescription
namestringYesTask name (max 253 chars)
functionNamestringNoExported WASM function to invoke; defaults to the task name if omitted
kindstandard \federatedNostandard
filebytesNoWASM binary encoded as a base64 string in YAML
imageUrlstringNoOCI image reference containing the WASM module; required for k8s proplets
cliArgs[]stringNoCommand-line arguments passed to the WASM runtime
inputs[]stringNoInputs passed to the function; numeric values are accepted and coerced
envmap[string]stringNoEnvironment variables available in the module
broadcastboolNofalseSend the task to all available proplets simultaneously instead of one
modeinfer \trainNo
metadataobjectNoArbitrary key-value JSON attached to the task and forwarded to the proplet
propletSelector.propletIdstringNoTarget a specific Proplet by its Kubernetes resource name
propletSelector.matchLabelsmap[string]stringNoTarget Proplets matching all labels
propletSelector.matchDeviceTypes[]stringNoTarget Proplets with a matching device type
propletSelector.matchCapabilities[]stringNoTarget Proplets advertising all listed capabilities
preferredPropletTypek8s \external \anyNo
resourceRequirements.cpustringNoCPU resource request/limit applied to the k8s Job pod (e.g. 100m); not used for scheduling
resourceRequirements.memorystringNoMemory resource request/limit applied to the k8s Job pod (e.g. 256Mi); not used for scheduling
resourceRequirements.custommap[string]stringNoCustom resource constraints passed to the k8s Job pod
daemonboolNofalseRun indefinitely; sets Kubernetes RestartPolicy to Always
restartPolicyKubernetes RestartPolicyNoOverride pod restart policy for k8s tasks
encryptedboolNofalseSignal that the WASM binary is encrypted
kbsResourcePathstringNoKey Broker Service path for decrypting the module
dependsOn[]stringNoTask IDs that must complete before this task is scheduled
runIfsuccess \failureNo
workflowIdstringNoGroups tasks into a workflow
jobIdstringNoAssociates this task with a PropellerJob
schedulestringNoCron expression for recurring execution
isRecurringboolNofalseEnable recurring execution via schedule
timezonestringNoUTCTimezone for cron evaluation
priorityintNo50Scheduling priority (0–100; higher = more urgent)
monitoringProfile.enabledboolNofalseEnable metrics collection during execution
monitoringProfile.intervaldurationNoMetrics collection interval (e.g. 10s, 1m)
monitoringProfile.collectCpuboolNoCollect CPU usage
monitoringProfile.collectMemoryboolNoCollect memory usage
monitoringProfile.collectDiskIoboolNoCollect disk I/O
monitoringProfile.collectThreadsboolNoCollect thread count
monitoringProfile.collectFileDescriptorsboolNoCollect file descriptor count
monitoringProfile.historySizeintNoNumber of history entries to retain
monitoringProfile.exportToMqttboolNoStream metrics to MQTT
monitoringProfile.retainHistoryboolNoRetain metrics history after task completes

Task Status Reference

FieldDescription
phasepending, scheduled, running, completed, failed, skipped, or interrupted
assignedPropletName of the Proplet executing this task
createdAtWhen the Task was first processed
startedAtWhen execution began
finishedAtWhen execution ended
updatedAtLast status update timestamp
nextRunNext scheduled execution time (recurring tasks)
resultsReturn value from the WASM function
errorError message when phase is failed
conditions[Scheduled]True when a proplet has been assigned
conditions[Started]True when execution began
conditions[Completed]True when execution finished successfully
latestMetricsMost recent CPU and memory sample from the proplet during execution

PropellerJob

A PropellerJob groups multiple Tasks into a single managed batch. The PropellerJobReconciler creates a child Task CR for each entry in spec.tasks, sets the jobId field on each to the PropellerJob's UID, and then monitors those tasks to completion. The short name for this resource is pjob (use kubectl get pjob as a shortcut).

PropellerJob Lifecycle

PropellerJob Lifecycle State Machine

Figure: PropellerJob state machine. Jobs start in Pending, transition to Running when child Tasks are created, and finally move to Completed when all tasks finish successfully or to Failed if any task fails.

PropellerJob Spec Reference

FieldTypeRequiredDefaultDescription
namestringYesJob name
executionModeparallel \sequential \configurableNo
tasks[]TaskSpecNoInline task definitions to create
taskRefs[]stringNoNames of existing Task resources to include

PropellerJob Status Reference

FieldDescription
phasePending, Running, Completed, or Failed
startTimeWhen the job began
finishTimeWhen the job completed
createdAtWhen the PropellerJob was first processed
updatedAtLast status update timestamp
taskCountTotal number of tasks
completedCountTasks in completed phase
failedCountTasks in failed phase
skippedCountTasks in skipped phase
interruptedCountTasks in interrupted phase
conditionsKubernetes condition array for detailed state

FederatedJob

A FederatedJob coordinates a multi-round federated learning experiment across a set of proplets. The FederatedJobReconciler creates TrainingRound CRs sequentially, one per round, advancing to the next round only when the current one completes aggregation.

FederatedJob Lifecycle

FederatedJob Lifecycle State Machine

Figure: FederatedJob state machine for multi-round federated learning experiments. Jobs begin in Pending, move to Running as TrainingRounds are created and executed sequentially, and complete when all rounds finish aggregation. Jobs fail if any round times out or encounters an aggregation error.

FederatedJob Spec Reference

FieldTypeRequiredDescription
experimentIdstringNoIdentifier for this federated learning experiment
modelRefstringNoReference to the initial global model
taskWasmImagestringYesOCI image containing the WASM training task
participants[]ParticipantSpecYesList of proplet IDs that participate in each round
hyperparamsobjectNoArbitrary hyperparameter values passed to each participant
kOfNintYesMinimum number of participant updates required before aggregation
timeoutSecondsintNoMaximum seconds per round before failing
rounds.totalintYesTotal number of training rounds
rounds.strategystringNoRound execution strategy
aggregator.algorithmstringNoAggregation algorithm (fedavg, concat)
aggregator.configobjectNoAlgorithm-specific configuration

FederatedJob Status Reference

FieldDescription
phasePending, Running, Completed, or Failed
currentRoundIndex of the round currently executing
completedRoundsNumber of rounds that have completed aggregation
aggregatedModelRefReference to the final aggregated model
participantsPer-participant status across all rounds
conditionsKubernetes condition array

TrainingRound

A TrainingRound represents a single round within a FederatedJob. The TrainingRoundReconciler creates one Task per participant, collects their model updates via MQTT result messages, and aggregates them once the k-of-n threshold is reached.

TrainingRound Lifecycle

TrainingRound Lifecycle State Machine

Figure: TrainingRound state machine showing the four-phase lifecycle. Rounds start in Pending, move to Running when participant Tasks are created, transition to Aggregating once k-of-n updates are received, and finally reach Completed after successful model aggregation. Rounds fail on timeout or aggregation errors.

TrainingRound Spec Reference

FieldTypeDescription
roundIdstringRound identifier
federatedJobRefLocalObjectReferenceParent FederatedJob
modelRefstringGlobal model reference for this round
taskWasmImagestringOCI image containing the participant training task
participants[]stringProplet IDs to include in this round
hyperparamsobjectHyperparameter values forwarded to tasks
kOfNintMinimum updates needed before aggregation
timeoutSecondsintRound timeout in seconds

TrainingRound Status Reference

FieldDescription
phasePending, Running, Aggregating, Completed, or Failed
startTime, endTimeRound timing
updatesReceivedNumber of participant updates collected so far
updatesRequiredThe k-of-n threshold
participantsPer-participant status (propletId, taskRef, status, updateReceived)
aggregatedModelRefReference to the round's aggregated result
conditionsKubernetes condition array

Scheduler

The scheduler runs inside the TaskReconciler to select a proplet for each task. It implements a three-phase algorithm:

  1. Filter (SelectCandidateProplets) — retains only proplets that are in Running phase and satisfy all criteria in spec.propletSelector.
  2. Score (Score) — assigns a numeric score to each candidate (lower = preferred).
  3. Pick (Pick) — selects the candidate with the lowest score; ties are broken deterministically.

Scheduler Algorithm

Figure: Three-phase scheduler algorithm. First, the Filter phase removes proplets that don't meet selector criteria or aren't in Running phase. Then, the Score phase assigns numeric scores to candidates (the default round-robin scorer assigns 0.1 to the next-in-line proplet and 1.0 to others). Finally, the Pick phase selects the lowest-scoring candidate for task assignment.

The default implementation is round-robin: the proplet immediately after the last-selected one receives a score of 0.1; all others receive 1.0. This distributes tasks evenly across available proplets over time.

Errors returned by the scheduler:

ErrorMeaning
ErrNoPropletNo proplets exist in the namespace
ErrNoCandidatesNo proplets satisfy the task's selector criteria

Owner References and Garbage Collection

The operator sets Kubernetes owner references on all resources it creates:

ParentOwned resources
PropellerJobTask CRs created from inline task specs
FederatedJobTrainingRound CRs
TrainingRoundTask CRs (one per participant)
Proplet (k8s type)Deployment
Task (k8s proplet)Job, ConfigMap

When a parent resource is deleted, Kubernetes garbage-collects all owned children automatically. For Proplet and Task resources, the operator also adds a finalizer to ensure it can clean up resources before the object is removed from the API server.

On this page