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 Kubernetesapp.Run()entry points directly as Go function calls. Ano_embedded_executorbuild 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. TheTestmethod reuses the sameTestFunccontract used in theExecutor.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.
SetHealthCheckis 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 thepkg/daemons/configtypes package from the concretepkg/cluster.Clusterimplementation, 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
configandcluster, which is an idiomatic Go pattern. Thereset boolparameter onBootstrapavoids a separateReset()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()) plusSyncandStartfor cache synchronization and controller startup. Stored inControlRuntimeas 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/wranglerandk3s-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
configpackage rather than using concrete wrangler types avoids a hard dependency on the wrangler code generator in every package that touchesControlRuntime.
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
ReadCloserinsideReadWriteCloserfollows 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.Storeinterface 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 whenStart()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) andDriver(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.
ReadWriteCloserembedsReadCloser, exactly mirroring theiostdlib pattern.DeferredStoreembeds bothoci.Store(third-party) andio.Closer(stdlib). No deep embedding chains.Implicit satisfaction: Interfaces are defined by providers (in the
executor,managed,proxy, andstorepackages) 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 theExecutorandDriverinterfaces which coordinate startup across packages.stdlib interfaces used:
io.Closer(embedded inDeferredStore),http.Handler(parameter toDriver.Register()). Theio.ReadWriteClosernaming convention is deliberately echoed instore.ReadWriteCloser.
Key abstractions#
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 singleinit()call registers the full runtime, and a singleexecutor.Set()invocation can replace it entirely for testing.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.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.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.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 stdlibiohierarchy exactly.
Interface-driven extensibility#
k3s uses interfaces for extensibility in three specific ways:
Executor swap-out (build tags +
init()registration): TheExecutorinterface 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.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.HTTP handler wrapping in the
Driverinterface: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.