Plugin System Architecture
The Flyte plugin system allows you to extend the platform's execution capabilities by mapping specific task types (like python, container, or spark) to specialized execution logic. This architecture ensures that the core Flyte engine remains agnostic of the specific details of how a task runs, whether it's a simple Go function or a complex Kubernetes resource.
Plugin Registration
To add a new task type to Flyte, you register a plugin using a PluginEntry. This entry acts as a manifest that tells Flyte which task types the plugin handles and how to initialize it.
Registration typically happens in an init() block within the plugin's package. For example, the core-sleep plugin in flyteplugins/go/tasks/plugins/core/sleep/plugin.go registers itself like this:
func init() {
pluginmachinery.PluginRegistry().RegisterCorePlugin(
core.PluginEntry{
ID: "core-sleep",
RegisteredTaskTypes: []core.TaskType{"core-sleep"},
LoadPlugin: func(ctx context.Context, iCtx core.SetupContext) (core.Plugin, error) {
return &Plugin{
taskStartTimes: make(map[string]time.Time),
}, nil
},
IsDefault: false,
},
)
}
The PluginEntry struct (defined in flyteplugins/go/tasks/pluginmachinery/core/plugin.go) contains:
- ID: A unique identifier for the plugin.
- RegisteredTaskTypes: A list of task types this plugin is responsible for.
- LoadPlugin: A lazy-loading function called by the registry to initialize the plugin instance.
- IsDefault: If true, this plugin will be used for any task type that doesn't have a specific mapping.
The Plugin Interface
Every plugin must implement the Plugin interface found in flyteplugins/go/tasks/pluginmachinery/core/plugin.go. This interface defines the lifecycle of a task execution:
type Plugin interface {
GetID() string
GetProperties() PluginProperties
Handle(ctx context.Context, tCtx TaskExecutionContext) (Transition, error)
Abort(ctx context.Context, tCtx TaskExecutionContext) error
Finalize(ctx context.Context, tCtx TaskExecutionContext) error
}
Execution Lifecycle
- Handle: This is the primary entry point called during every reconciliation loop. It must be idempotent and non-blocking. It returns a
Transitionindicating the next state of the task (e.g.,Running,Success, orFailure). - Abort: Called when a task needs to be stopped (e.g., the workflow was cancelled). Like
Handle, it must be idempotent. - Finalize: Always called after
HandleorAbortto perform cleanup, such as releasing resources or deleting temporary files.
The Plugin Registry
The Registry class in executor/pkg/plugin/registry.go is the central coordinator that manages these plugins within the Flyte executor. It wraps the global plugin registry and provides a mapping from task types to their respective implementations.
Initialization
During executor startup (typically in executor/setup.go), the Registry is initialized. This process iterates through all registered PluginEntry objects and calls their LoadPlugin functions.
// From executor/pkg/plugin/registry.go
func (r *Registry) Initialize(ctx context.Context) error {
// ...
for _, entry := range r.pluginRegistry.GetCorePlugins() {
plugin, err := pluginsCore.LoadPlugin(ctx, r.setupCtx, entry)
if err != nil {
return fmt.Errorf("failed to load core plugin %s: %w", entry.ID, err)
}
for _, taskType := range entry.RegisteredTaskTypes {
r.plugins[taskType] = plugin
}
// ...
}
r.initialized = true
return nil
}
Plugin Resolution
When the TaskAction controller needs to execute a task, it calls ResolvePlugin to find the correct implementation:
func (r *Registry) ResolvePlugin(taskType string) (pluginsCore.Plugin, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if p, ok := r.plugins[taskType]; ok {
return p, nil
}
if r.defaultPlugin != nil {
return r.defaultPlugin, nil
}
return nil, fmt.Errorf("no plugin registered for task type %q", taskType)
}
Core vs. Kubernetes Plugins
Flyte distinguishes between two primary types of plugins:
- Core Plugins: These are implemented entirely in Go and run within the Flyte executor process. The
core-sleepplugin is a prime example. They are ideal for lightweight logic or interacting with external APIs. - Kubernetes (K8s) Plugins: These plugins manage Kubernetes resources (like Pods, Jobs, or CRDs). They use a simplified
k8s.Plugininterface.
To maintain a consistent interface for the executor, the Registry wraps K8s plugins in a PluginManager (found in executor/pkg/plugin/k8s/plugin_manager.go). The PluginManager adapts the K8s-specific logic into the standard core.Plugin interface, handling resource creation, status polling, and event watching automatically.
Implementation Considerations
- Idempotency: Because Flyte uses a reconciliation loop,
Handle,Abort, andFinalizemay be called multiple times for the same task execution. Your implementation must handle this gracefully. - Lazy Loading: The
LoadPluginfunction is only called once during theRegistry.Initializephase. Use this to set up shared resources like clients or caches. - Task Type Collisions: If two plugins register for the same task type, the
Registrywill log a warning and the last one registered will take precedence. Avoid collisions by using unique, descriptive task type strings.