K3s — API Surface#
API types#
CLI + Supervisor HTTP API + Embedded Kubernetes API (proxied)
k3s exposes three distinct API surfaces: a rich CLI for operators and administrators, a custom HTTP “supervisor” API used internally by k3s agents to bootstrap and communicate with the server, and the full upstream Kubernetes API server (proxied through the supervisor port). There are no gRPC services or public library packages.
CLI#
Framework#
github.com/urfave/cli/v2 — not Cobra. This is a notable divergence from the Kubernetes ecosystem norm (Cobra + pflag). k3s uses urfave/cli for its CLI layer but supplements it with github.com/spf13/pflag for early argument scanning (data-dir, debug, prefer-bundled-bin) before the CLI library is fully initialized.
Config file preprocessing#
Before urfave/cli parses flags, pkg/configfilearg.MustParse(os.Args) scans for --config/-c, reads the YAML file, and expands its key-value pairs into equivalent CLI flags. This means the CLI surface and YAML config surface are 1:1 — no separate config schema.
Top-level commands#
The k3s binary is a multicall dispatcher. The same binary is invoked as different tool names via symlinks (crictl, kubectl, ctr). As a CLI app, the root commands are:
| Command | Description | Implementation |
|---|---|---|
k3s server | Start a k3s server (control plane + embedded agent by default) | Internal binary k3s-server via exec |
k3s agent | Start a k3s agent (worker node only) | Internal binary k3s-agent via exec |
k3s kubectl | Embedded kubectl (delegates to extracted binary) | External binary |
k3s crictl | Embedded crictl (delegates to extracted binary) | External binary |
k3s ctr | Embedded ctr (delegates to extracted containerd binary) | External binary |
k3s check-config | Validate kernel config for k3s compatibility | External binary |
k3s token | Manage bootstrap tokens | Internal binary |
k3s etcd-snapshot | Manage etcd snapshots | Internal binary |
k3s secrets-encrypt | Manage secrets encryption | Internal binary |
k3s certificate | Manage TLS certificates | Internal binary |
k3s completion | Generate shell completion scripts | Internal binary |
Subcommand structure#
k3s token (pkg/cli/cmds/token.go):
create— Create a bootstrap token with TTL, groups, usagesdelete— Delete a tokengenerate— Generate a token string without creating it on the serverlist— List tokens (with-o jsonoption)rotate— Rotate the server token
k3s etcd-snapshot (pkg/cli/cmds/etcd_snapshot.go):
save— Trigger immediate snapshotdelete— Delete named snapshot(s)ls(aliases:list,l) — List snapshots (with-o jsonoption)prune— Remove snapshots exceeding configured retention count
k3s secrets-encrypt (pkg/cli/cmds/secrets_encrypt.go):
status— Show current encryption statusenable— Enable encryption at restdisable— Disable encryption at restprepare— Prepare for key rotationrotate— Rotate the current encryption keyreencrypt— Re-encrypt all secrets with the current keyrotate-keys— Rotate encryption provider keys (calls Kubernetes KMS)
k3s certificate (pkg/cli/cmds/certs.go):
check— Validate certificate expiryrotate— Rotate expiring certificatesrotate-ca— Rotate CA certificates
Flag patterns#
- Global flags:
--debug(alsoK3S_DEBUGenv),--data-dir/-d(alsoK3S_DATA_DIRenv) k3s serverflags: 100+ flags covering networking, storage, TLS, feature gates, and component enable/disable. All are in thecmds.Serverstruct (pkg/cli/cmds/server.go).k3s agentflags: ~50 flags covering node identity, networking, and containerd config. All incmds.Agentstruct (pkg/cli/cmds/agent.go).- Environment variable binding: Key flags have
EnvVars: []string{version.ProgramUpper + "_KUBECONFIG_OUTPUT"}etc., using theK3S_prefix convention throughout. - No automatic env→flag binding framework (no Viper): each flag explicitly lists its env var names.
REST/HTTP — Supervisor API#
k3s runs a custom HTTP server on the supervisor port (default 6443 in single-port mode, configurable via --supervisor-port). This is k3s’s own API — distinct from the Kubernetes API server that also runs on port 6443. The two are multiplexed: the supervisor NotFoundHandler falls through to the embedded Kubernetes API server handler.
Router#
github.com/gorilla/mux wrapped in a thin k3s mux.Router type (pkg/util/mux/). mux.Router adds a .Use(middlewares...) API on top of gorilla’s router, enabling per-router middleware stacks.
Route registration#
All routes are registered in pkg/server/handlers/router.go:NewHandler(). Routes are hard-coded (no annotation or convention-based discovery). The router uses nested NotFoundHandler chaining to implement layered auth: unauthed → agent-authed → node-authed → server-authed → system-privileged.
Middleware chain (layered auth model)#
The supervisor API uses nested routers rather than a linear middleware chain. Each layer handles a different auth level and delegates non-matching paths inward:
router (no auth)
└── systemAuthed (system:masters RBAC group required)
└── serverAuthed (k3s:server group required)
└── nodeAuthed (system:nodes group required)
└── authed (k3s:agent, system:nodes, or bootstrap-token group required)
└── NotFoundHandler → Kubernetes APIServerEach layer adds auth.HasRole(control, ...), auth.RequestInfo(), and auth.MaxInFlight() middleware via mux.Use().
The auth.Delegated() middleware (used for spegel/metrics/pprof sub-routers) delegates authentication/authorization to the Kubernetes API server via SubjectAccessReview.
Authentication#
auth.HasRole()callscontrol.Runtime.Authenticator.AuthenticateRequest(req), which is a k3s-specific authenticator that validates bearer tokens and client certificates against the cluster’s CAauth.IsLocalOrHasRole()short-circuits auth for requests from127.0.0.1/::1auth.Delegated()uses Kubernetes RBAC SubjectAccessReview for components like spegel and pprof
Key endpoints#
All k3s-specific endpoints are under /v1-k3s/ (the prefix uses the program name for rebrandability):
| Endpoint | Auth tier | Method | Description |
|---|---|---|---|
GET /ping | None | GET | Liveness check — returns pong |
GET /cacerts | None | GET | Serve server CA certificate bundle |
GET /static/ | None | GET | Serve static files from data dir |
GET /v1-k3s/serving-kubelet.crt | Agent | GET/POST | Issue/sign serving cert for kubelet; POST body = CSR |
GET /v1-k3s/client-kubelet.crt | Agent | GET/POST | Issue/sign client cert for kubelet |
GET /v1-k3s/client-kube-proxy.crt | Agent | GET/POST | Issue/sign client cert for kube-proxy |
GET /v1-k3s/client-k3s-controller.crt | Agent | GET/POST | Issue/sign client cert for k3s controller |
GET /v1-k3s/client-ca.crt | Agent | GET | Serve client CA bundle |
GET /v1-k3s/server-ca.crt | Agent | GET | Serve server CA bundle |
GET /v1-k3s/apiservers | Agent | GET | Return list of apiserver endpoint addresses (JSON) |
GET /v1-k3s/config | Agent | GET | Return agent/server configuration (JSON) |
GET /v1-k3s/readyz | Agent | GET | Readiness check — 200 OK when core is initialized |
GET /v1-k3s/connect | Node | CONNECT | WebSocket tunnel for node ↔ server communication |
GET /v1-k3s/encrypt/status | Server | GET | Return current secrets encryption status |
PUT /v1-k3s/encrypt/config | Server | PUT | Update secrets encryption configuration |
PUT /v1-k3s/cert/cacerts | Server | PUT | Replace CA certificates |
GET/POST /v1-k3s/server-bootstrap | Server | GET/POST | Serve/receive HA bootstrap data (etcd members) |
POST /v1-k3s/token | Server | POST | Issue a new agent join token |
CONNECT / | System | CONNECT | Raw WebSocket tunnel (system:masters) |
Handler design: Each handler is a function returning http.Handler (closed over config.Control), not a method on a controller struct. Certificate endpoints use a GET/POST dual pattern: GET returns the legacy shared key + cert; POST with a CSR body returns only the signed cert using the CSR’s public key.
Additional per-node HTTP endpoints (agent nodes)#
Agent nodes expose additional HTTP servers registered via the RouterFunc pattern:
| Component | Router | Endpoints |
|---|---|---|
pkg/spegel | registry.Router (agent + server) | GET /v2/ — OCI registry mirror (p2p distributed image cache) |
pkg/spegel | registry.Router | GET /v1-k3s/p2p — libp2p peer discovery |
pkg/metrics | metrics.Router (agent + server) | GET /metrics — Prometheus metrics |
pkg/profile | pprof.Router (agent + server) | GET /debug/pprof/* — Go pprof profiling |
gRPC API#
Not present in k3s’s own code. k3s contains no .proto files and registers no gRPC servers. gRPC appears only in test files (pkg/etcd/etcd_linux_test.go) where mock etcd gRPC servers are stood up to test the k3s etcd client integration. The embedded etcd server itself uses gRPC internally, but that is upstream etcd code, not k3s code.
Plugin / Extension system#
k3s has one explicit extension point at the Go level, plus a structural extension mechanism:
StartupHook (Go API)#
Package: pkg/cli/cmds/ (types), pkg/server/server.go (execution)
type StartupHookArgs struct {
APIServerReady <-chan struct{}
KubeConfigSupervisor string
Skips map[string]bool
Disables map[string]bool
}
type StartupHook func(context.Context, *sync.WaitGroup, StartupHookArgs) errorHooks are registered in cmds.Server.StartupHooks []StartupHook and are called after the API server is ready but before the server is considered fully started. This is an in-process extension point — only usable by code that imports k3s packages and builds a custom binary (not by external plugins). Hooks are used by integration tests and by downstream distributions (e.g., RKE2) to register additional controllers.
RouterFunc (Go API)#
Package: pkg/spegel/, pkg/metrics/, pkg/profile/
type RouterFunc func(ctx context.Context, nodeConfig *config.Node) (*mux.Router, error)Components register themselves by setting a package-level Router variable of type RouterFunc. The agent/server startup code calls these during initialization to attach sub-routers to the node’s HTTP listener. This is the pattern used by spegel (embedded registry mirror), metrics, and pprof.
Embedded executor (build-tag extension)#
The Executor interface in pkg/daemons/executor/ (17 methods) is designed as an extension point. The no_embedded_executor build tag allows building k3s without the upstream Kubernetes components, enabling alternative executor implementations. This is a compile-time extension mechanism used by the test suite and potentially by downstream distributions.
Kubernetes add-on controllers (runtime extension)#
k3s bundles two controller-based extension mechanisms that run as Kubernetes controllers:
- HelmChart CRD controller (
k3s-io/helm-controller): WatchesHelmChartandHelmChartConfigcustom resources and renders/applies Helm charts. This is the primary way to extend k3s with additional cluster functionality — users create HelmChart resources rather than runninghelm install. - Auto-deploy manifests (
pkg/deploy): Files placed in/var/lib/rancher/k3s/server/manifests/are auto-applied on startup. k3s ships default manifests for coredns, traefik, local-path-provisioner, metrics-server, and ccm.
Library API#
k3s is not designed as a library. There are no public packages intended for import by third parties. The package structure (all under github.com/k3s-io/k3s/pkg/) is technically importable but:
- The
cmds.Server.StartupHooksfield is the one documented extension point for downstream forks (e.g., RKE2 builds on top of k3s by importing its packages and adding hooks) - There is no versioning strategy or stability guarantee for any packages
- The
Executorinterface is the other “intended” seam, but only accessible via build tags and blank imports
API design observations#
Rebrandability via
version.Program: All k3s-specific HTTP paths, environment variables, RBAC group names, and annotation keys useversion.Program(default"k3s") rather than hardcoded strings. This allows downstream distributions like RKE2 to rebrand by changing this constant at build time.Certificate issuance as an API: The supervisor API’s most architecturally interesting feature is its certificate-signing service. Agents authenticate with a join token, then receive signed TLS certificates for all Kubernetes components on-demand. The dual GET/POST pattern (shared key vs. CSR-based) provides a migration path from the legacy shared-key model to a more secure CSR-based model.
Layered NotFoundHandler chain: The nested router auth model is unusual. Rather than a flat middleware chain, each auth tier is a separate gorilla/mux router with its
NotFoundHandlerpointing to the next tier. This makes the auth model explicit in code structure but makes it harder to trace the full middleware chain for any given request.No public REST API versioning: The k3s supervisor API has no version negotiation, content negotiation, or deprecation strategy. It is a private, versioned-by-release API. Only agents built from the same version are expected to talk to it.
Uniform flag naming: The
k3s servercommand has 100+ flags but follows consistent naming conventions:--disable-<component>to remove built-in components,--extra-<component>-argto pass additional flags to embedded Kubernetes components, andK3S_<FLAG_NAME>environment variable equivalents for all major flags.