01
An OS that is an API
Talos Linux is a Linux distribution with one job: run Kubernetes. It is immutable (the root filesystem is a read-only image), minimal (roughly a dozen binaries of its own), and has no SSH, no shell and no package manager. Every interaction, from reading a log to reconfiguring the network, is an authenticated gRPC call handled by
machined, which runs as PID 1. You manage nodes the way you manage Kubernetes objects: declare, apply, let controllers converge.talosctl → where does the call go?
gRPC · mTLS · :50000-e endpoint
-n nodes
$talosctl -e 10.0.0.10 -n 10.0.0.12 logs kubelet
step 0/9
Press run. The request is gRPC over mTLS: talosctl presents the client certificate from your talosconfig.(output appears here)
-e vs -n
The endpoint is not the node
Your talosconfig lists endpoints (usually the control-plane IPs, or a load balancer). Commands go to an endpoint's apid, which proxies to the nodes you name with
-n. That means workers don't need to be reachable from your laptop, and one command can fan out to many nodes, each reply tagged with its node. Authorization is in the client certificate: os:admin, os:operator, os:reader.~/.talos/config (talosconfig)
context: homelab
contexts:
homelab:
endpoints: [10.0.0.10, 10.0.0.11, 10.0.0.12] # control planes
nodes: [10.0.0.10] # default -n
ca: LS0tLS1CRUdJTi... # Talos API CA
crt: LS0tLS1CRUdJTi... # your client cert (role: os:admin)
key: LS0tLS1CRUdJTi...everyday talosctl
talosctl config endpoint 10.0.0.10 10.0.0.11 10.0.0.12
talosctl -n 10.0.0.21 dashboard # live TUI
talosctl -n 10.0.0.21 services # what machined runs
talosctl -n 10.0.0.21 logs kubelet -f
talosctl -n 10.0.0.21 dmesg
talosctl -n 10.0.0.21,10.0.0.22 get addresses
talosctl -n 10.0.0.21 read /proc/meminfo # read-only file access
talosctl -n 10.0.0.21 pcap -i eth0 # packet capture, no tcpdump installthe whole process list
What actually runs on a node
On a traditional distro the OS is a pile of packages that Kubernetes happens to run on. On Talos the OS is a small, fixed set of services that exist only to run the kubelet and, on control planes, etcd.
| where | what it does | |
|---|---|---|
| machined | PID 1, every node | Replaces systemd and the whole userland. Runs services, holds the config, runs the COSI controllers that drive network, disks, time, etc. |
| apid | :50000, every node | The gRPC front door. Terminates mTLS, checks the role in your client cert, and proxies to other nodes' apid. |
| trustd | :50001, control planes | Issues certificates to other nodes (e.g. a worker's apid) after they prove they hold the cluster's machine token. |
| containerd | every node | Two instances: one for Talos system services, one (CRI) for the kubelet's pods. |
| kubelet | every node | Runs as a container managed by machined, configured from machine.kubelet. |
| etcd | control planes | Managed by machined, not by Kubernetes. Data lives on EPHEMERAL at /var/lib/etcd. |
| udevd, auditd, … | every node | A handful of small helpers. That's the whole list — there is no sshd, bash, or apt. |
imperative vs declarative
Fixing a node without logging into it
The absence of SSH isn't a restriction you work around; it removes the most common source of drift. Every fix becomes a change to declared config (which you keep in git) or a new image, so the next node you build is identical.
Same incident, two operating models
Incident: A node goes NotReady. The kubelet keeps restarting after someone set a bad flag.
Traditional nodeSSH + package manager + mutable files
ssh admin@node-7needs an SSH daemon, keys and a user on every nodesudo systemctl status kubeletinspect the unitsudo journalctl -u kubelet -n 200read the logssudo vim /var/lib/kubelet/config.yamlhand-edit a file on one nodesudo systemctl restart kubeletnode-7 now differs from its siblings
Talos nodetalosctl + declarative config
talosctl -n 10.0.0.17 service kubeletservice state + recent events, over the APItalosctl -n 10.0.0.17 logs kubeletsame log stream, no shellvim patches/kubelet.yaml # in gitfix the declared config, not the nodetalosctl -n 10.0.0.17 patch mc --patch @patches/kubelet.yamlmachined restarts kubelet with the new config — no reboot
steps (trad)
5
steps (talos)
4
drift introduced
2
drift on talos
0
immutable by construction
Disk layout: one mutable partition
Talos boots a SquashFS root image from its initramfs into memory, read-only. On disk there are only a few partitions with narrowly defined purposes, so you always know which bytes matter: the config in
STATE, workload data in EPHEMERAL.Install disk & memory
click a partition · pick an operationin memoryloaded from the initramfs on every boot
/ (rootfs)SquashFS · read-only
machined, apid, trustd, containerd, runc… roughly a dozen binaries. The kubelet itself runs from a container image.
/systemtmpfs
runtime state: sockets, generated configs
/etc, /usr …read-only (a few files bind-mounted)
no package manager could write here even if it existed
install disk (GPT)
STATE/system/state
The machine configuration (config.yaml) and node identity. Can be encrypted (TPM / KMS / static key).
The only thing that defines the node. Survives upgrades; wiping it returns the node to maintenance mode.
tip
Click a partition to see what it holds.GRUB vs systemd-boot
Older and BIOS installs use GRUB with A/B kernels on the BOOT partition. Since Talos 1.10, new installs on UEFI machines use systemd-boot, where each slot is a Unified Kernel Image (kernel + initramfs + cmdline, optionally signed for Secure Boot) on the EFI partition. The A/B idea is the same.COSI: the Kubernetes model, inside the OS
Everything is a resource
machined is built on COSI (Common Operating System Interface): typed resources in namespaces, and controllers that watch some resources and write others. Specs describe what should be, statuses record what is. It's the same reconcile loop as a Kubernetes controller, which is why Talos self-heals instead of running scripts once at boot.
A controller loop, one event at a time
inputs
you / the world
talosctl, cables, pods
MachineConfig
v4
controllers (in machined)
AddressConfigController
watch → diff → act
AddressSpecController
watch → diff → act
AddressStatusController
watch → diff → act
LinkStatusController
watch → diff → act
resources · talosctl get …
AddressSpecv0
eth0/10.0.0.50/24 · absent
AddressStatusv0
eth0/10.0.0.50/24 · absent
LinkStatusv7
eth0 · operState: up
NodeAddressv3
current · [10.0.0.17/24]
kernel
Linux kernel
netlink: eth0
eth0 up
10.0.0.17/24
10.0.0.17/24
Pick an event and trigger it. Each controller only watches resources and writes resources; the chain emerges from those watches.
reading resources
talosctl get rd # list every resource type
talosctl -n 10.0.0.21 get links
talosctl -n 10.0.0.21 get addresses
talosctl -n 10.0.0.21 get routes
talosctl -n 10.0.0.21 get members # cluster membership (discovery)
talosctl -n 10.0.0.21 get machineconfig -o yaml
talosctl -n 10.0.0.21 get addresses --watch # stream changes$ talosctl -n 10.0.0.21 get addresses
NODE NAMESPACE TYPE ID VERSION ADDRESS LINK
10.0.0.21 network AddressStatus eth0/10.0.0.21/24 1 10.0.0.21/24 eth0
10.0.0.21 network AddressStatus eth0/fe80::be24:11/64 2 fe80::be24:11/64 eth0
10.0.0.21 network AddressStatus lo/127.0.0.1/8 1 127.0.0.1/8 lo
10.0.0.21 network AddressStatus cilium_host/10.244… 1 10.244.1.94/32 cilium_host- why it mattersThere is no back door: the gRPC API is the only way in, so everything is authenticated, role-scoped and auditable, and a compromised workload has no shell to land in.
- why it mattersThe read-only root and the narrow disk layout mean a node is fully described by its image version plus its machine config. Reproducing or replacing a node is routine.
- why it mattersCOSI controllers continuously reconcile desired and actual state, so configuration is applied (and re-applied after drift) by the OS itself, not by an external tool.