K3s — Interfaces#

Interface catalog#

Executor#

  • Package: github.com/k3s-io/k3s/pkg/daemons/executor
  • File: pkg/daemons/executor/executor.go:31
  • Methods (17):
    Bootstrap(ctx context.Context, nodeConfig *daemonconfig.Node, cfg cmds.Agent) error
    Kubelet(ctx context.Context, args []string) error
    KubeProxy(ctx context.Context, args []string) error
    APIServerHandlers(ctx context.Context) (authenticator.Request, http.Handler, error)
    APIServer(ctx context.Context, args []string) error
    Scheduler(ctx context.Context, nodeReady <-chan struct{}, args []string) error
    ControllerManager(ctx context.Context, args []string) error
    CurrentETCDOptions() (InitialOptions, error)
    ETCD(ctx context.Context, wg *sync.WaitGroup, args *ETCDConfig, extraArgs []string, test TestFunc) error
    CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error
    Containerd(ctx context.Context, node *daemonconfig.Node) error
    Docker(ctx context.Context, node *daemonconfig.Node) error
    CRI(ctx context.Context, node *daemonconfig.Node) error
    CNI(ctx context.Context, wg *sync.WaitGroup, node *daemonconfig.Node) error
    APIServerReadyChan() <-chan struct{}
    ETCDReadyChan() <-chan struct{}
    CRIReadyChan() <-chan struct{}
    IsSelfHosted() bool
  • Purpose: The central architectural seam between k3s orchestration logic and the upstream Kubernetes component lifecycle. Each method corresponds to one Kubernetes subsystem (apiserver, scheduler, controller-manager, etcd, kubelet, kube-proxy, containerd, CNI). The three *ReadyChan() methods expose channel-based readiness signals so that the startup sequencer can block until each component is available.
  • Implementations: pkg/executor/embed.Embedded — the only production implementation; calls upstream Kubernetes app.Run() entry points directly as Go function calls. A no_embedded_executor build tag allows building without the upstream k8s dependency (e.g., for testing or alternative CRI environments).
  • Design quality: Deliberately broad — 17 methods is a violation of the Interface Segregation Principle if viewed naively, but the design is intentional. This is a registry interface, not a consumer interface: the entire set of k8s components is known at compile time, and the single concrete implementation is registered once via init(). The width is a direct consequence of the “all components in one process” architecture. The package-level forwarding functions (executor.APIServer(...), executor.Kubelet(...)) shadow the interface methods with nil-guard boilerplate, giving callers a clean call site without needing to hold the interface value directly.

Driver (managed cluster)#

  • Package: github.com/k3s-io/k3s/pkg/cluster/managed
  • File: pkg/cluster/managed/drivers.go:16
  • Methods (12):
    SetControlConfig(config *config.Control) error
    IsInitialized() (bool, error)
    Register(handler http.Handler) (http.Handler, error)
    Reset(ctx context.Context, wg *sync.WaitGroup, rebostrap func() error) error
    IsReset() (bool, error)
    ResetFile() string
    Start(ctx context.Context, wg *sync.WaitGroup, clientAccessInfo *clientaccess.Info) error
    Restore(ctx context.Context) error
    EndpointName() string
    Snapshot(ctx context.Context) (*SnapshotResult, error)
    ReconcileSnapshotData(ctx context.Context) error
    GetMembersClientURLs(ctx context.Context) ([]string, error)
    RemoveSelf(ctx context.Context) error
    Test(ctx context.Context, enableMaintenance bool) error
  • Purpose: Abstracts the lifecycle management of the embedded cluster storage backend (etcd). Includes HTTP handler registration (for serving cluster data to joining nodes), snapshot operations (backup/restore), HA membership management, and health testing. Drivers register themselves into a package-level slice via RegisterDriver().
  • Implementations: pkg/etcd.ETCD — the only known implementation; manages embedded etcd with S3-compatible snapshot support.
  • Design quality: Well-designed for the use case. The driver registry pattern (slice of Driver) anticipates future alternative backends. Register(handler http.Handler) (http.Handler, error) follows the HTTP middleware pattern — the driver wraps the existing handler, which is idiomatic and composable. The Test method reuses the same TestFunc contract used in the Executor.ETCD() call, maintaining consistency.

Proxy (agent API proxy)#

  • Package: github.com/k3s-io/k3s/pkg/agent/proxy
  • File: pkg/agent/proxy/apiproxy.go:15
  • Methods (9):
    Update(addresses []string)
    SetAPIServerPort(port int, isIPv6 bool) error
    SetSupervisorDefault(address string)
    IsSupervisorLBEnabled() bool
    SupervisorURL() string
    SupervisorAddresses() []string
    APIServerURL() string
    IsAPIServerLBEnabled() bool
    SetHealthCheck(address string, healthCheck loadbalancer.HealthCheckFunc)
  • Purpose: Manages the agent’s view of server endpoints. Abstracts whether the agent is speaking to a server directly or through a local load-balancer sidecar. Allows runtime reconfiguration of the supervisor and API server addresses as cluster membership changes. The “Proxy” name is somewhat misleading — the interface manages URL resolution, not actual network proxying (which is handled by pkg/agent/loadbalancer.LoadBalancer).
  • Implementations: proxy.proxy (unexported concrete struct in the same package). NewSupervisorProxy() returns the interface, hiding whether a load-balancer was started.
  • Design quality: Reasonable consumer-facing interface. The dual URL tracking (supervisor vs. API server, which may be on different ports) is reflected cleanly in the interface. SetHealthCheck is slightly surprising to find here — it delegates to the underlying load-balancers — but is acceptable as a convenience pass-through.

Cluster#

  • Package: github.com/k3s-io/k3s/pkg/daemons/config
  • File: pkg/daemons/config/types.go:390
  • Methods (3):
    Bootstrap(ctx context.Context, reset bool) error
    ListenAndServe(ctx context.Context) error
    Start(ctx context.Context, wg *sync.WaitGroup) error
  • Purpose: Lifecycle interface for the cluster storage subsystem embedded in ControlRuntime. Decouples the pkg/daemons/config types package from the concrete pkg/cluster.Cluster implementation, preventing an import cycle. Three methods cover the full lifecycle: cluster data bootstrap, HTTP supervisor endpoint startup, and full cluster start.
  • Implementations: pkg/cluster.Cluster
  • Design quality: Excellent ISP compliance — minimal three-method interface. Defined in the shared types package specifically to break the import cycle between config and cluster, which is an idiomatic Go pattern. The reset bool parameter on Bootstrap avoids a separate Reset() lifecycle method.

K3sFactory / CoreFactory / DiscoveryFactory#

  • Package: github.com/k3s-io/k3s/pkg/daemons/config
  • File: pkg/daemons/config/types.go:396–412
  • Methods (each has 3 methods):
    // K3sFactory
    K3s() k3s.Interface
    Sync(ctx context.Context) error
    Start(ctx context.Context, defaultThreadiness int) error
    
    // CoreFactory
    Core() core.Interface
    Sync(ctx context.Context) error
    Start(ctx context.Context, defaultThreadiness int) error
    
    // DiscoveryFactory
    Discovery() discovery.Interface
    Sync(ctx context.Context) error
    Start(ctx context.Context, defaultThreadiness int) error
  • Purpose: Wrangler-generated controller factory interfaces embedded in ControlRuntime. Each factory exposes a typed accessor (K3s(), Core(), Discovery()) plus Sync and Start for cache synchronization and controller startup. Stored in ControlRuntime as interface fields, which allows them to be nil-checked before use and makes the runtime struct testable without concrete wrangler factories.
  • Implementations: Wrangler-generated concrete types from rancher/wrangler and k3s-io/k3s/pkg/generated/controllers.
  • Design quality: Structurally uniform (all three follow the same factory pattern), which is a consequence of wrangler’s code generation. Defining them as interfaces in the shared config package rather than using concrete wrangler types avoids a hard dependency on the wrangler code generator in every package that touches ControlRuntime.

ReadCloser / ReadWriteCloser (etcd MVCC store)#

  • Package: github.com/k3s-io/k3s/pkg/etcd/store
  • File: pkg/etcd/store/store.go:29–40
  • Methods:
    // ReadCloser
    List(ctx context.Context, key string, rev int64) ([]mvccpb.KeyValue, error)
    Get(ctx context.Context, key string) (mvccpb.KeyValue, error)
    Close() error
    
    // ReadWriteCloser (embeds ReadCloser)
    Create(ctx context.Context, key string, value []byte) error
    Update(ctx context.Context, key string, revision int64, value []byte) error
    Delete(ctx context.Context, key string, revision int64) error
  • Purpose: Thin, composable interface hierarchy for the etcd/kine MVCC store used during cluster bootstrap and snapshot restore. The split between read-only and read-write access follows the principle of least privilege — read-only access is sufficient for most bootstrap consumers.
  • Implementations: store.RemoteStore (etcd client wrapper), store.LocalStore (direct MVCC store wrapper). Compile-time check: var _ ReadWriteCloser = &RemoteStore{}.
  • Design quality: Well-segregated. The embedding of ReadCloser inside ReadWriteCloser follows stdlib precedent (io.ReadCloser, io.ReadWriteCloser). The explicit compile-time interface check (var _ ReadWriteCloser = &RemoteStore{}) is a good practice that appears in both implementations.

DeferredStore (Spegel OCI registry)#

  • Package: github.com/k3s-io/k3s/pkg/spegel
  • File: pkg/spegel/store.go:18
  • Methods:
    // Embeds oci.Store and io.Closer
    Start() error
    // Plus all oci.Store methods (Name, ListImages, etc.)
    // Plus Close() from io.Closer
  • Purpose: Extends the spegel/pkg/oci.Store interface to support deferred initialization. k3s starts the Spegel embedded OCI registry before containerd is fully ready; this interface allows the store to be created early and connected to the backend later when Start() is called. Methods return errors until started.
  • Implementations: spegel.deferredStore (package-private). Compile-time check: var _ DeferredStore = &deferredStore{}.
  • Design quality: Clean extension pattern — adding one method (Start()) to an existing third-party interface. The deferred initialization pattern (returns errors until started) is an unusual but pragmatic solution to the startup ordering problem specific to the OCI registry integration.

Interface patterns#

  • Size distribution: Skewed toward medium-to-large interfaces, reflecting k3s’s role as an orchestrator rather than a library. Executor (17 methods) and Driver (14 methods) are large by Go standards, but both represent complete subsystem lifecycles. Cluster (3 methods), ReadCloser (3 methods), and factory interfaces (3 methods each) are small and well-segregated.

  • Embedding: Present and idiomatic. ReadWriteCloser embeds ReadCloser, exactly mirroring the io stdlib pattern. DeferredStore embeds both oci.Store (third-party) and io.Closer (stdlib). No deep embedding chains.

  • Implicit satisfaction: Interfaces are defined by providers (in the executor, managed, proxy, and store packages) rather than by consumers. This is slightly unusual in Go — the more idiomatic approach is to define small interfaces at the point of use. k3s’s pattern reflects the need to have centralized, named contracts that multiple callers depend on, particularly for the Executor and Driver interfaces which coordinate startup across packages.

  • stdlib interfaces used: io.Closer (embedded in DeferredStore), http.Handler (parameter to Driver.Register()). The io.ReadWriteCloser naming convention is deliberately echoed in store.ReadWriteCloser.


Key abstractions#

  1. Executor — The single most important interface in the codebase. It defines the contract between k3s’s orchestration layer and the entire Kubernetes runtime. Without this interface, all of k3s’s startup sequencing, readiness tracking, and build-tag-based component exclusion would be impossible. The 17-method width is a feature, not a flaw: it means a single init() call registers the full runtime, and a single executor.Set() invocation can replace it entirely for testing.

  2. Driver (managed) — Second most significant. It abstracts the cluster storage backend so that both the etcd and kine paths can be managed uniformly. The HTTP handler wrapping method (Register) is particularly elegant — it integrates the storage backend into the HTTP supervisor without requiring a global handler registry.

  3. Cluster — A small but critical import-cycle-breaking interface. The pattern of defining a minimal interface in a shared types package to break cycles between two concrete packages is a common Go idiom; k3s applies it cleanly here.

  4. Proxy (agent proxy) — Captures the agent’s runtime relationship with the server cluster. The abstraction over “direct connection vs. local load-balancer” simplifies all agent-side code that needs to construct API server or supervisor URLs.

  5. ReadCloser / ReadWriteCloser — The cleanest interfaces in the codebase in terms of ISP adherence. They enable etcd and kine stores to be used interchangeably during bootstrap, following the stdlib io hierarchy exactly.


Interface-driven extensibility#

k3s uses interfaces for extensibility in three specific ways:

  1. Executor swap-out (build tags + init() registration): The Executor interface combined with a build tag (//go:build !no_embedded_executor) allows the embedded Kubernetes runtime to be compiled out entirely. A different executor implementation (e.g., one that manages external processes or stubs for testing) can be registered by providing an alternate blank-import package. This is a compile-time plugin mechanism rather than a runtime one.

  2. Managed driver registry: managed.RegisterDriver(d Driver) accumulates drivers into a package-level slice. managed.Default() returns the first registered driver. This allows future alternative storage backends to register themselves with minimal coupling to the rest of the codebase.

  3. HTTP handler wrapping in the Driver interface: Driver.Register(handler http.Handler) (http.Handler, error) follows the decorator pattern — the storage driver can intercept HTTP requests by wrapping the upstream handler. This is how the etcd driver injects its cluster-data endpoint into the supervisor HTTP stack without modifying server startup code.

Runtime plugin-style extensibility (e.g., hashicorp/go-plugin, gRPC, WASM) is absent — consistent with k3s’s design philosophy of a single statically-linked binary.