Technical Documentation: siderolabs/talos
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
Talos is a modern OS for running Kubernetes: secure, immutable, and minimal.
Talos is fully open source, production-ready, and supported by the people at Sidero Labs.
All system management is done via an API - there is no shell or interactive console.
Benefits include:
- Security: Talos reduces your attack surface: It's minimal, hardened, and immutable.
All API access is secured with mutual TLS (mTLS) authentication. - Predictability: Talos eliminates configuration drift, reduces unknown factors by employing immutable infrastructure ideology, and delivers atomic updates.
- Evolvability: Talos simplifies your architecture, increases your agility, and always delivers current stable Kubernetes and Linux versions.
Documentation
For instructions on deploying and managing Talos, see the Documentation.
Community
- Support: Questions, bugs, feature requests GitHub Discussions
- Slack: Join our slack channel. Request access via inviter.co.
- Twitter: @SideroLabs
If you're interested in this project and would like to help in engineering efforts or have general usage questions, we are happy to have you!
We hold a monthly meeting that all audiences are welcome to attend.
We would appreciate your feedback so that we can make Talos even better!
To do so, you can take our survey.
Office Hours
- When: Second Monday of every month at 16:30 UTC.
- Where: Google Meet.
Note: You can convert the meeting hours to your local time.
Contributing
Contributions are welcomed and appreciated!
See Contributing for our guidelines.
License
Some software we distribute is under the General Public License family of licenses or other licenses that require we provide you with the source code.
If you would like a copy of the source code for this software, please contact us via email: info at SideroLabs.com.
Overview (`api/signer/README.md`)
Signer API
The signer API defines the gRPC service used by the Talos imager to delegate
SecureBoot and PCR signing to an out-of-process signer.
An example YubiKey-based implementation is available at:
https://github.com/jaakkonen/talos-secureboot
Overview (`hack/ctrltrace-viewer/README.md`)
ctrltrace viewer
A single-page viewer for the controller runtime traces produced byinternal/pkg/ctrltrace. It replays a capture as an animated graph: controllers and
resource types as nodes, clustered by namespace, flashing as they reconcile, with
resource nodes growing and shrinking as instances come and go.
Using it
Open index.html in a browser โ straight off the filesystem is fine โ then drop a.jsonl capture onto the page, or use Load a trace. Nothing is uploaded; the file
is parsed in the browser.
To capture one, see the tracer's README:
make talos WITH_DEBUG=1
# boot with talos.trace=1 talos.trace.specs=1
talosctl -n <node> read /run/talos-trace.jsonl > talos-boot.jsonlThe page also tries to fetch talos-boot.jsonl from its own directory on load, so
dropping a capture there and serving the directory gives you a viewer that opens
straight onto that boot:
cp talos-boot.jsonl hack/ctrltrace-viewer/
python3 -m http.server -d hack/ctrltrace-viewer 8000Themes
The Theme control in the footer picks between Auto (follows the browser), Light,
Dark and Projector; t cycles them, and the choice is remembered per browser.
Projector is the one to use on stage: white ground, near-black ink, deep blue and rust
marks instead of the screen palette's lighter steps, thicker strokes and larger type. Lecture
hall projectors crush low contrast, so the greys that read as pleasantly quiet on a laptop
disappear entirely โ this theme pushes everything toward black and scales every stroke,
node and label by 1.45x.
What it shows
- Squares are controllers, circles are resource types; size follows the number
of live instances. Position is a force layout clustered by namespace, computed once
and frozen so the map stays put between replays. - A node is an empty outline until the trace first touches it, so the boot fills in the
architecture rather than starting fully lit. - Selecting a node draws its edges as arrows in the direction data flows โ orange out
of the controller that writes a resource, blue from a resource into the controllers
that read it. The side panel lists the same thing in text, along with the live
instances at the current playhead; clicking an instance shows its spec, when the
capture was taken withtalos.trace.specs=1. - The graph is the union of the static dependency graph embedded in the trace (the same
onetalosctl inspect dependenciesrenders) and the traffic actually observed, which
in practice adds a few hundred edges the controllers never declared. - The scrub track is state operations per second on a square-root scale โ a boot is
heavy-tailed enough that a linear axis shows one spike and nothing else.
Notes
- The page loads d3 from cdnjs and its fonts from Google Fonts, so first paint wants a
network connection. Everything else, including parsing a multi-megabyte trace, is local. - Large captures are fine: a 6 MB, 29k-event trace parses, lays out and renders in well
under a second.
Overview (`tools/cloud-image-uploader/README.md`)
cloud-image-uploader
Image tags
Published images are tagged with their build type, which is what cleanup tooling keys off
of to decide how long an image should stick around:
- AWS: the tag
BuildType, on both the AMI and the EBS snapshot backing it. - GCP: the label
build-typeon the image. GCP label keys must be lowercase, hence the
different spelling.
The values are the same on both:
| Value | Meaning |
|---|---|
e2e |
throwaway image built for an e2e run |
nightly |
branch build, versioned by git describe (e.g. v1.14.0-alpha.1-101-g11a7fbe4c) |
release |
tagged release, including alpha/beta/rc pre-releases |
The value is derived from --tag and --name-prefix.
vmimport role
Role should be pre-created before running this command.
aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.jsonGoogle Cloud Pre-requisites
GOOGLE_PROJECT_ID- Google Cloud Project IDGOOGLE_CREDENTIALS_JSON- Google Cloud Service Account JSON
Overview (`tools/kubeimportlinter/README.md`)
kubeimportlinter
kubeimportlinter checks that versioned Kubernetes imports are aliased to a descriptive name.
Imports whose final path segment is a bare API version (e.g. v1, v1beta1, v2) from a Kubernetes host (k8s.io, sigs.k8s.io, ...) must be renamed:
import "k8s.io/api/core/v1" // invalid: package name is bare v1
import v1 "k8s.io/api/core/v1" // invalid: alias equals the version
import corev1 "k8s.io/api/core/v1" // validIt runs as a golangci-lint module plugin custom linter named kubeimportlinter.
Usage
The repo-root .custom-gcl.yml lists this module for the custom binary build:
make golangci-lint-custom
_out/custom-gcl run --config .golangci.ymlEnable it in .golangci.yml:
linters:
enable:
- kubeimportlinter
settings:
custom:
kubeimportlinter:
type: module
settings:
exclude:
- "**/*_test.go"
- "vendor/**"
rules:
versioned_imports:
# override the default host list if needed
hosts:
- k8s.io
- sigs.k8s.io
allow:
- "path/to/exempt.go"Config
All rules are enabled by default. An empty settings block applies the rule to
every analyzed Go file under the repository root.
- the repository root is the directory containing the
golangci-lintconfig file. versioned_imports.hostslists the import path prefixes the rule applies to
(defaults tok8s.io,sigs.k8s.io).allowexempts files from the rule;excludeis a broader file skip;includenarrows the rule;enabled: falsedisables it.- globs are repo-relative, slash-separated, and support
**.
Inline exceptions
// kubeimportlint:ignore versioned_imports intentional bare alias
v1 "k8s.io/api/core/v1"Overview (`tools/loglinter/README.md`)
log-linter
log-linter checks Go code for logging policy violations.
It runs as a golangci-lint module plugin custom linter named loglinter.
Current checks:
- disallowed
log/slogimports unless allowlisted - disallowed stdlib
log.*calls unless allowlisted - printf-style formatting directives in
*zap.Loggermessage strings fmt.Sprintf(...)used as a zap message- ad hoc zap root logger constructors not wrapped with
.With(logging.Component(...))in the same expression
Usage
Build a custom golangci-lint binary
A repo-root .custom-gcl.yml is included for the custom binary build.
From the repo root:
make golangci-lint-customThat builds the binary through the Docker-based flow and outputs it to _out/custom-gcl.
Run it directly:
_out/custom-gcl run --config .golangci.ymlEnable the plugin in `.golangci.yml`
Add a custom linter entry like this:
version: "2"
linters:
enable:
- loglinter
settings:
custom:
loglinter:
type: module
description: checks logging conventions
settings:
exclude:
- "**/*_test.go"
- "_out/**"
- "vendor/**"
rules:
slog_imports:
allow:
- "internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_supported.go"The repository root is the directory containing the golangci-lint config file.
Config
All rules are enabled by default. An empty settings block applies every rule to every analyzed Go file under the repository root.
Use config mainly to carve out exceptions.
allowis the recommended way to exempt files from a specific rule.excludeis a broader file skip for a rule or for the whole run.includeis still supported when you intentionally want to narrow a rule, but the default is repo-wide enforcement.enabled: falseexplicitly disables a rule.- globs are repo-relative, slash-separated, and support
**.
Inline exceptions
A single issue can be ignored with a same-line or immediately preceding comment:
// loglint:ignore stdlib_log_calls compatibility adapter
log.Printf("allowed here")Multiple rules can be listed as a comma-separated token:
// loglint:ignore stdlib_log_calls,zap_root_component reasonUse all to suppress every rule for the next line or same line.
Notes
- Once
loglinteris enabled in.golangci.yml, use the custom binary (./custom-gcl) ormake golangci-lint-custom; a stockgolangci-lintbinary will not have the plugin compiled in. - The root-component check is intentionally conservative: it only accepts
.With(logging.Component(...))when that wrapper appears in the same expression as the configured constructor call. - Constructor names and component helper calls are configurable.
Overview (`internal/pkg/ctrltrace/README.md`)
ctrltrace โ controller runtime tracing
ctrltrace records what the COSI controller runtime actually does while Talos boots:
which controller woke up, what it read, what it wrote, and when every resource appeared,
changed and went away.
It is a development facility, and it is compiled in only under WITH_DEBUG=1 (build tagsidero.debug). A production image gets stub.go instead: no tracer, no state wrapping, no
way to turn it on. Even in a debug build nothing is wrapped or allocated until the trace is
explicitly enabled, so it costs nothing on a normal boot.
Capturing a trace
Build a debug image:
make talos WITH_DEBUG=1Then boot the node with the trace kernel arguments:
talos.trace=1Optional:
| parameter | default | meaning |
|---|---|---|
talos.trace.path |
/run/talos-trace.jsonl |
output file |
talos.trace.specs |
0 |
also record resource specs (YAML) on create/update |
talos.trace.duration |
unlimited | stop tracing after e.g. 90s, keeping the file small |
In a container-based cluster (talosctl cluster create --provisioner docker) set theTALOS_TRACE=1 environment variable on the container instead.
For a QEMU cluster:
talosctl cluster create \
--extra-boot-kernel-args "talos.trace=1 talos.trace.specs=1 talos.trace.duration=3m"Pull the trace off the node once it is up:
talosctl -n <node> read /run/talos-trace.jsonl > talos-boot.jsonlHandle the file with care
talos.trace.specs=1 records the YAML of every resource created or updated, and unlike the
API the tracer does not redact the types Talos marks Sensitivity: sensitive โ a secret
you cannot read is not much use when you are debugging how it came to exist. So a capture with
specs contains root CA private keys, machine tokens and the rest, in the clear. This is why the
tracer is a WITH_DEBUG=1 facility aimed at throwaway clusters.
The file is written 0600, but once it is off the node it is just a file. Even without specs
it records resource IDs โ node names, cluster membership, affiliate IDs, certificate SANs.
Don't hand a capture around, and don't take one from a cluster you care about.
What is in the file
Newline-delimited JSON, one record per line, in chronological order.
{"k":"meta","op":"start"}โ header: format version, Talos version, wall clock oft=0.{"k":"graph","op":"dependencies"}โ the static controller/resource graph, the same onetalosctl inspect dependenciesrenders. Embedding it makes the trace self-contained.{"k":"ctrl",...}โ controller lifecycle:start,wake(the controller picked up a
reconcile event),queue,inputs,stop,crash.{"k":"op",...}โ a state operation:get,list,create,update,destroy,watch,watchkind, with the resource coordinates, owner, version, phase, finalizers,
duration and error.{"k":"meta","op":"stop"}โ footer: events written and dropped.
t is microseconds since the tracer started. Field names are short on purpose โ a boot
produces a lot of these.
How the attribution works
Writes are attributed by the resource owner, but reads are not owned by anybody. To attribute
them, WrapController tags the context it hands to each controller's Run with the controller
name; that tag rides along into every Get/List/Modify the controller makes and is read
back by the state wrapper. Operations with no tag (the API server, the v1alpha1 sequencer) are
recorded for writes only โ reads from those would drown out the controllers.
The runtime restarts a failed controller by calling Run again with the same context and
runtime values, and controllers may rely on that identity โ internal/pkg/dns.Manager
panics outright if ServeBackground is handed a different context on a restart. So the
wrapper builds the tagged context and the runtime wrapper once per controller and reuses
them; re-tagging per Run breaks that contract, and would also leave a second event relay
behind on every restart, competing with the first for the same upstream channel.
Controller wake-ups are exact: the wrapper relays the reconcile event channel, and an
unbuffered hand-off completes precisely when the controller enters a new reconcile cycle. Where
that cycle ends is not observable from outside the controller, so it is reconstructed offline
from the controller's last state operation before its next wake-up.
Run the package tests with the tag; without it only the stub test runs:
go test -tags sidero.debug ./internal/pkg/ctrltrace/...Caveats
- A spec longer than 8 KiB is cut off, and the tail is marked
# ... truncated. - Events are dropped rather than blocking a controller when the queue (64k) overflows; the
footer reports how many. - Tracing starts before
/runis mounted, so early events sit in the queue until the file
can be opened. - With the tracer on, the state wrapper does not implement the optional
Teardowner/TeardownAndDestroyerfast paths, so teardowns decompose intoget+update. This is
visible in the trace and is the only behavioral difference.
Viewing a trace
hack/ctrltrace-viewer is a single-page viewer for
these files: open its index.html and drop the capture onto it. It draws the controllers
and resource types as a graph clustered by namespace and replays the capture โ controllers
flash as they reconcile, resource nodes grow with their live instance count, read and write
pulses travel the edges, and selecting a node shows what reads and writes it.
Overview (`internal/pkg/efivarfs/README.md`)
efivarfs
This package was copied from the upstream repository at efivarfs
There were minimal changes done to comply with the linter and gofmt rules.
The major change was to remove the EFI variable file immutable attribute before writing to it.
Overview (`internal/pkg/msguid/README.md`)
msguid
This package was copied from the upstream repository at msguid
There were minimal changes done to comply with the linter and gofmt rules.
Overview (`internal/app/machined/pkg/controllers/hypervisor/testdata/libvirt/README.md`)
libvirt domain schemas
Generated by make generate from libvirt 12.7.0.
Source archive SHA-256: 7ec1a04e7e4f4069353d4daac117bbe869287f5b202695de61fe1b079efb6cb6 (pinned by siderolabs/extensions/hypervisors/vars.yaml).
The included RNG files form the transitive domain.rng include closure.
COPYING and COPYING.LESSER are the upstream license texts.
Update LIBVIRT_VERSION and LIBVIRT_SHA256 in Makefile together, then run make generate.
--- METRICS ---
- Files Extracted: 10
- Estimated Token Budget: ~4877 tokens
- Canonical Reference: https://codewiki.google/github.com/siderolabs/talos