Bare metal
This page turns a spare tower or mini-PC into a single-node Kubernetes cluster. You need two USB sticks. One stick installs the operating system. The other stick tells the node what it is.
For a dedicated server that you reach through the rescue system of a provider, see rescue mode. For a VM, see Proxmox or the quick start.
A CI run tests the Proxmox page, but no CI run tests this page. Nothing in the project boots the ISO on physical hardware. If your machine does not behave as a step here says, open an issue.
Before you start
- A 64-bit x86 machine that boots UEFI. Corium boots UEFI only.
- A whole disk that you can lose. The installer erases its target disk. If the machine has more than one disk, disconnect the others.
- Two USB sticks. The stick for the ISO must be 4 GB or more, because the ISO is about 2.4 GB. The stick for the seed can be any size.
- Wired ethernet. Corium does not configure wireless.
- 4 GB of RAM and 32 GB of disk. 8 GB of RAM is better.
- A public SSH key.
How the install is shaped
Two separate things happen:
- The ISO installs the operating system. This step needs no operator. Anaconda deploys the bootc image that is inside the ISO. It shows no prompts, and you do not write a kickstart file. It never asks what the node is.
- cloud-init configures the node on its first real boot. cloud-init applies
the
corium:block and creates your login account.
A desktop PC has no metadata service for step 2. You supply the data from a
NoCloud seed: a USB stick with a filesystem that has the label CIDATA.
Corium looks for this datasource first, and it is the only datasource that works
offline. You can also install without a seed, and that state is
recoverable.
1. Write the ISO to a stick
Download corium-0.5.0-x86_64.iso and verify it.
downloads
gives all three download routes and the verification steps.
Run the listing command first. If dd writes to the wrong device, it destroys
that device.
# linux
lsblk -o NAME,SIZE,MODEL,TRAN
sudo dd if=corium-0.5.0-x86_64.iso of=/dev/sdX bs=4M status=progress oflag=sync# macOS
diskutil list
diskutil unmountDisk /dev/diskN
sudo dd if=corium-0.5.0-x86_64.iso of=/dev/rdiskN bs=4mOn macOS, use /dev/rdiskN and not /dev/diskN. For a write of this size, the
raw device is approximately ten times faster.
A graphical writer also works, for example Rufus, balenaEtcher, or GNOME Disks. The writer must copy the image exactly as it is. In Rufus, use DD image mode, not ISO mode.
2. Write the configuration seed
A complete single-node cluster:
#cloud-config
corium:
role: single
users:
- name: core
groups: [wheel]
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-ed25519 AAAA... you@example.comrole is the only required field. See
examples for workers,
add-ons, and a custom CNI. See the
reference for every field.
If you have a checkout of the repository, validate the file before you go to the
machine. Validation works offline and reports all problems at once. mise exec
supplies the pinned Go toolchain, so the command works on a machine that never
installed Go:
mise exec -- go run ./cmd/corium-agent validate node.yamlPut two files on a filesystem with the label CIDATA. cloud-init looks for this
label. Nothing else about the stick is important.
# linux. /dev/sdY is the *second* stick, not the one holding the ISO.
sudo mkfs.vfat -n CIDATA /dev/sdY1
sudo mount /dev/sdY1 /mnt
sudo cp node.yaml /mnt/user-data
printf 'instance-id: corium-01\nlocal-hostname: corium-01\n' | sudo tee /mnt/meta-data
sudo umount /mnt# macOS
diskutil eraseDisk FAT32 CIDATA MBRFormat /dev/diskN
cp node.yaml /Volumes/CIDATA/user-data
printf 'instance-id: corium-01\nlocal-hostname: corium-01\n' > /Volumes/CIDATA/meta-dataBoth files must be at the root, with exactly these names. meta-data can be
almost empty, but it must exist. If it does not exist, cloud-init rejects the
seed.
3. Set up the firmware
Open the setup screen. Usually you press Del, F2, or F12 at power-on. Set
these three values:
| Setting | Value | Why |
|---|---|---|
| Boot mode | UEFI, CSM/legacy off | Corium boots UEFI only |
| Boot order | Internal disk first, USB second | See below |
| Secure Boot | Off, if the install does not boot | See below |
Disk first, USB second is correct. An empty disk has no UEFI boot entry, so the firmware goes to the stick and installs. After the install, the disk has an entry and boots first. If the stick is first, the machine installs itself again at every reboot. The install shows no prompts, so nothing on the screen tells you.
Secure Boot is not tested on bare metal. The image includes the signed shim from Fedora, so Secure Boot can work. If the machine does not boot after the install, turn Secure Boot off.
4. Install
Connect only the ISO stick, and turn on the machine. The installer then has one less disk to look at. Anaconda reboots when it is done, after a few minutes on an SSD. Remove the stick during that reboot.
5. Configure it
Connect the seed stick and boot the machine.
cloud-init finds CIDATA and creates your user. Then corium-bootstrap reads
the corium: block and starts k0s. The node is Ready approximately one minute
after power-on. The console banner shows the role of the node, its cluster, and
whether k0s is running.
When the node is up, you can remove the seed stick or leave it in.
6. Check it worked
ssh core@<the node's address>
sudo k0s kubectl get nodesFor the kubeconfig and the next steps, continue with the
quick start. Management of the node with
cctl is a separate step. The management API is off by default,
and you must claim a node before it answers.
If you install without a seed
The seed is optional. Without a seed, the ISO installs a node that waits for its configuration. The node shows a pairing code on its console, and you can claim it from your own machine. The seed stick is the path that needs no operator, but it is not the only path.
| After the install | |
|---|---|
corium-bootstrap | Ran, found nothing, wrote no marker |
corium-apid | Serving on 7443, enrolment open, pairing code on the console |
| k0s | Not started. A node nobody has claimed is in no cluster |
| Accounts | None. Corium creates no default user, and PermitRootLogin is no |
The console shows the code and the fingerprint. Use the fingerprint to make sure that you claim the correct node:
This node is not part of a cluster.
address 192.168.0.42
pairing K4M2-8QT9
fingerprint SHA256:...
cctl enroll 192.168.0.42 --code K4M2-8QT9Run this command from your own machine. You must already have an operator PKI. cctl shows how to make one.
cctl enroll 192.168.0.42 --code K4M2-8QT9 --config node.yamlnode.yaml is the same document as the seed in step 2, with the users: block.
The users: block gives the machine its first account. The node pins your CA,
builds the role, and starts as a cluster.
This is the default since ADR 11. Before ADR 11, a machine installed this way had no account and no API. The only way in was to go back to the machine with a USB stick.
The seed stick still works, and it is still the better choice when you have
one. It needs no operator: you read no console and type no code. Your CA owns
the node from its first boot, not only after the minute between boot and claim.
Connect a CIDATA stick and reboot. The node wrote no bootstrap marker, so
step 5 runs as if it were the first boot.
Know these two limits before you rely on enrolment:
- It closes when the node bootstraps. You cannot claim a node that built its
role, whatever its configuration says. This is intentional. It stops a person
who reads the screen from taking over a machine that runs your workloads. As a
result, you can claim a node configured by seed only during the minute that
it takes to come up. After that, you must use
cctl resetto take it over. - It needs the console. The pairing code is on a channel that an attacker on the network cannot see. All of its security comes from this. On a headless machine with no IPMI, that channel is a monitor that you must go and connect.
corium.config=<url> on the kernel command line is the third option, and the
weakest. It configures the node but creates no account. The result is a cluster
that you cannot log in to.
What goes wrong on a desktop that does not on a hypervisor
It installs itself again on every boot. The boot order causes this. Put the disk first and USB second, and remove the ISO stick after the install.
The node comes up with no user and no cluster. cloud-init did not find the
seed. Make sure that the filesystem label is exactly CIDATA. Make sure that
user-data and meta-data are both at the root of the filesystem. Make sure
that user-data starts with the #cloud-config line. On the node,
sudo cloud-init query --all shows which datasource cloud-init selected.
The node has an address but resolves no names. The cause is a static address
without a resolver. Keep the machine on DHCP for the first boot. If the address
must be static, set it in the network: block of cloud-init, and give that
block its own nameserver.
Nothing on the console after the firmware. The cause is almost always the boot mode. A firmware in CSM/legacy mode finds no bootloader on a UEFI-only image. Its message makes the machine look dead.
For other problems, see troubleshooting. It lists them by symptom.