K3s — Structure#
Layout pattern#
Custom — Dual-Binary Distribution Layout
K3s uses a variation of Standard Go Layout (cmd/ + pkg/) with a critical twist: it maintains two separate main entry points — a root main.go (the in-process embedded k8s binary) and cmd/k3s/main.go (the distribution launcher that extracts and dispatches). The internal/ directory is absent; all packages live under pkg/. There is no vendor/ directory. The layout reflects the project’s unusual distribution model: it ships a single binary that bootstraps by unpacking other binaries from embedded data assets.
Directory map#
k3s/
├── main.go # Root: unified in-process binary (embeds all k8s components)
├── cmd/ # Individual sub-command entry points
│ ├── k3s/main.go # Distribution launcher: multicall wrapper + binary extractor
│ ├── server/main.go # Standalone server subcommand binary
│ ├── agent/main.go # Standalone agent subcommand binary
│ ├── kubectl/main.go # Thin kubectl wrapper
│ ├── ctr/main.go # Thin ctr (containerd CLI) wrapper
│ ├── containerd/main.go # Containerd binary entry
│ ├── cert/main.go # Certificate management subcommand
│ ├── encrypt/main.go # Secret encryption subcommand
│ ├── etcdsnapshot/main.go # etcd snapshot subcommand
│ ├── completion/main.go # Shell completion subcommand
│ └── token/main.go # Token management subcommand
├── pkg/ # All Go packages (no internal/)
│ ├── agent/ # Node agent logic (CRI, CNI, flannel, tunnels)
│ ├── authenticator/ # Basic auth, password file, hash
│ ├── bootstrap/ # Server bootstrap data handling
│ ├── certmonitor/ # TLS certificate rotation watcher
│ ├── cgroups/ # cgroup detection helper
│ ├── cli/ # CLI command definitions (urfave/cli)
│ ├── clientaccess/ # Client credential negotiation
│ ├── cloudprovider/ # K3s cloud provider for Kubernetes
│ ├── cluster/ # HA cluster management, storage bootstrap
│ ├── configfilearg/ # Config file + CLI arg merging
│ ├── containerd/ # Containerd configuration generation
│ ├── ctr/ # ctr passthrough helper
│ ├── daemons/ # Core abstraction layer
│ │ ├── agent/ # Agent daemon setup
│ │ ├── config/ # Shared config types (Node, Agent, Control, etc.)
│ │ ├── control/ # Server control daemon
│ │ └── executor/ # Executor interface definition
│ ├── data/ # Embedded binary asset loading
│ ├── datadir/ # Data directory resolution
│ ├── dataverify/ # Binary integrity verification
│ ├── deploy/ # Addon/manifest deployer
│ ├── etcd/ # Embedded etcd lifecycle + S3 snapshots
│ ├── executor/ # Executor implementation
│ │ └── embed/ # Embedded executor (all k8s in-process)
│ ├── flock/ # File locking utility
│ ├── kubeadm/ # kubeadm token format helpers
│ ├── kubectl/ # kubectl passthrough helper
│ ├── metrics/ # Prometheus metrics helpers
│ ├── node/ # Node label/annotation controllers
│ ├── nodeconfig/ # Node configuration annotation
│ ├── nodepassword/ # Node password authentication
│ ├── passwd/ # Password file management
│ ├── proctitle/ # Process title setting
│ ├── profile/ # pprof server
│ ├── rootless/ # Rootless mode support
│ ├── rootlessports/ # Rootless port forwarding
│ ├── secretsencrypt/ # Kubernetes secrets encryption at rest
│ ├── server/ # HTTP server, API handlers, TLS setup
│ ├── signals/ # OS signal handling
│ ├── spegel/ # libp2p-based distributed registry mirror
│ ├── static/ # Embedded static files (helm charts, etc.)
│ ├── untar/ # Tar extraction utility
│ ├── util/ # Misc utilities
│ │ ├── bindata/ # Embedded binary data helpers
│ │ ├── errors/ # Error utilities
│ │ ├── home/ # Home directory resolution
│ │ ├── logger/ # Logging helpers
│ │ ├── metrics/ # Metrics utilities
│ │ ├── mux/ # HTTP multiplexer helpers
│ │ ├── permissions/ # File permission utilities
│ │ └── services/ # Kubernetes service helpers
│ ├── version/ # Version constants
│ └── vpn/ # WireGuard VPN integration
├── manifests/ # Kubernetes manifests deployed at startup
├── scripts/ # CI/build shell scripts
├── tests/ # All test types
│ ├── docker/ # Docker-based integration tests
│ ├── e2e/ # End-to-end tests (Vagrant/VM)
│ ├── fixtures/ # Test fixtures
│ ├── install/ # Install script tests
│ ├── integration/ # Go integration tests
│ ├── mock/ # Mock implementations
│ └── perf/ # Performance tests
├── docs/ # Architecture decision records, release notes
├── contrib/ # Contrib tools (ansible, test templates)
├── package/ # RPM packaging
├── updatecli/ # Dependency update automation
└── Makefile # Build orchestration (delegates to scripts/)Entry points#
K3s has a two-tier binary architecture — the distribution launcher and the in-process runtime:
Tier 1: Distribution Launcher (cmd/k3s/main.go)#
The binary shipped to end users. On invocation it:
- Detects if called via symlink (crictl, kubectl, ctr) and dispatches directly
- For internal sub-commands (server, agent), extracts a compressed data archive from embedded assets into a data directory
- Exec’s into the extracted k3s binary or delegates to bundled binaries
Tier 2: In-Process Runtime (main.go at root)#
The binary embedded inside the data archive. It runs all k8s components in-process:
k3s server→ starts API server, controller-manager, scheduler, etcd, and agent in-process goroutinesk3s agent→ starts kubelet, kube-proxy, containerd, flannel in-process goroutines- All other subcommands (
kubectl,crictl,etcdsnapshot,cert,token,secretsencrypt,completion) run as distinct extracted binaries
Individual sub-command binaries (cmd/*/main.go)#
Thin wrappers compiled as separate binaries that get embedded into the data archive:
cmd/server/main.go— server binary (embedded in archive)cmd/agent/main.go— agent binary (embedded in archive)cmd/kubectl/main.go— kubectl wrappercmd/ctr/main.go— ctr (containerd) wrappercmd/cert/main.go,cmd/encrypt/main.go,cmd/etcdsnapshot/main.go, etc.
Package organization#
Internal packages (all under pkg/ — no internal/ directory)#
Core abstraction layer:
pkg/daemons/config— Shared configuration types (Node,Agent,Control,ControlConfig) used across all components; the central data modelpkg/daemons/executor— Defines theExecutorinterface, the key architectural boundary between the CLI/control layer and the actual k8s component implementations
Server-side packages:
pkg/server— HTTP/HTTPS server setup, kubeconfig generation, API handler registrationpkg/cluster— HA cluster formation, storage backend bootstrapping, leader electionpkg/etcd— Embedded etcd lifecycle management, S3 snapshot upload/restore, snapshot schedulingpkg/deploy— Addon deployer: watches for YAML manifests in a directory and applies them via the k8s APIpkg/secretsencrypt— Secrets encryption configuration management
Agent-side packages:
pkg/agent— Node agent startup (containerd, flannel, tunnel, netpol, loadbalancer)pkg/agent/config— Agent configuration loading from serverpkg/agent/containerd— Containerd startup and configurationpkg/agent/flannel— Flannel CNI configurationpkg/agent/tunnel— WebSocket tunnel between agent and serverpkg/agent/loadbalancer— Client-side load balancer for server endpoints
Infrastructure packages:
pkg/executor/embed— ConcreteExecutorimplementation: runs kube-apiserver, kubelet, scheduler, etc. as in-process goroutines using theirapp.Run()entrypointspkg/configfilearg— Merges YAML config file arguments with CLI args beforeurfave/cliparses thempkg/clientaccess— Negotiates and stores server credentials on the agentpkg/bootstrap— Serializes/deserializes server bootstrap data for agent distribution
Utility packages:
pkg/version— Version constants (Program,Version,GitCommit)pkg/signals— OS signal handling (SIGTERM, SIGINT → context cancellation)pkg/flock— File-based locking to prevent concurrent data directory writespkg/spegel— Embedded OCI registry mirror via libp2p (Spegel integration)pkg/vpn— WireGuard VPN integration for pod networking
Public packages (pkg/)#
All packages are technically public (no internal/ restriction). In practice, the package structure is clearly layered for internal use; nothing here is designed as an importable library by external consumers.
Layering#
The package dependency graph follows a rough layered architecture:
CLI layer (pkg/cli/*, cmd/*)
↓
Server/Agent (pkg/server/*, pkg/agent/*)
↓
Daemon control (pkg/daemons/control/*, pkg/cluster/*)
↓
Executor (pkg/daemons/executor/ interface ← pkg/executor/embed/ impl)
↓
Config types (pkg/daemons/config/)
↓
Utilities (pkg/signals/, pkg/flock/, pkg/version/, pkg/util/*)The Executor interface in pkg/daemons/executor/ is the main architectural seam: everything above it is k3s-specific logic; everything below it (the embed implementation) is k8s upstream code called via Go function invocations.
Build system#
- Build tool: Docker-based build via
Makefile→scripts/build. Production builds happen entirely inside multi-stage Docker containers (Alpine-based) with static CGO linking. - Key targets:
make local-binary— Builds k3s binary and assets inside Dockermake ci— Runs full CI: validate, build, testscripts/build— Core build script: compiles all sub-binaries, creates the data archive (tar + zstd), embeds it into the main binary usinggo-bindata-style embeddingscripts/package— Packages the final binary into tarballs and RPMs
- Docker: Yes, multi-stage. The build image (
Dockerfile.local) installs all native deps (SQLite, btrfs, libseccomp, WireGuard) for static linking. The test image (Dockerfile.test) runs integration tests. - Output: A single statically-linked binary containing all sub-binaries and CNI plugins as a compressed embedded archive.
Notable structural decisions#
No
internal/directory: Every package is technically importable by external code. This is an unusual choice for a project of this size; it likely reflects k3s’s origins as a rapid extraction from a larger internal codebase and/or the need to allow the distribution launcher (cmd/k3s/) to import freely frompkg/.The
Executorinterface as a build-tag seam:pkg/daemons/executor/executor.godefines the interface;pkg/executor/embed/embed.goprovides the implementation, gated by//go:build !no_embedded_executor. This allows building k3s without the embedded k8s components — useful for testing or alternative executor implementations. The import of_ "github.com/k3s-io/k3s/pkg/executor/embed"inmain.gois a blank import that triggersinit()to register the concrete implementation.Two-tier binary design: The distribution binary (
cmd/k3s/) is a thin launcher that extracts embedded binaries; the extracted binary (main.go) runs everything in-process. This design allows k3s to ship a single file while still supporting exec-based isolation for certain commands (crictl, kubectl) that cannot share the same process.Data embedding via archive: The bundled binaries (kubectl, crictl, ctr, CNI plugins, etc.) are not embedded individually via
//go:embed— they are packed into a compressed tar archive and embedded as a single binary blob, then extracted to a versioned data directory at runtime. Thepkg/data/package handles this archive loading.pkg/configfileargpreprocessing: Rather than using a standard config-file library, k3s preprocessesos.Argsbefore passing them tourfave/cli. ThisMustParse()call (visible inmain.go) expands YAML config file keys into CLI flag equivalents, enabling a unified CLI/file config system without reimplementing flag parsing.tests/as a top-level peer: All test types (unit, integration, e2e, Docker, perf) live under a single top-leveltests/directory rather than colocated with source packages. This is unusual for Go projects and reflects k3s’s heavy reliance on VM-based E2E tests that cannot run alongside source packages.