Compare commits

...
Author SHA1 Message Date
claude-botandClaude Opus 5 a4885926e3 chore: destroy the Ubuntu template VM with saturn's conversion
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
VM 605 is pinned to saturn, so it cannot survive the conversion, and it has to
go before the node leaves the Proxmox cluster — afterwards the provider cannot
reach it and every plan errors.

Nothing uses it. Its purpose was cloning new Proxmox VMs, Proxmox is being
retired, and the one remaining VM definition sets template_name = null
explicitly rather than cloning from it.

The module default is changed to null for the same reason: it named a template
that no longer exists, which would only surface as an apply-time failure for
whoever created the next VM.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 22:43:26 +02:00
k.renner ca324825a9 Merge pull request 'chore: destroy VM 151 and drop saturn from the Talos image seeding' (#588) from remove-vm-151 into master
ci/woodpecker/push/deploy-infrastructure Pipeline failed
ci/woodpecker/cron/alert-monitor Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
Reviewed-on: k.renner/homelab#588
2026-08-25 20:16:24 +00:00
claude-botandClaude Opus 5 dd727769f6 chore: destroy VM 151 and drop saturn from the Talos image seeding
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
saturn is being converted to bare metal, so its VM definition goes and it
leaves the list of Proxmox nodes the pipeline seeds the nocloud image onto.

The apply must run while saturn is still a Proxmox cluster member. Once it
leaves, the provider cannot reach the node, the plan errors, and the only way
out is tofu state rm.

module.k8s_2 is referenced nowhere else in opentofu/, so this destroys exactly
one resource.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 22:15:12 +02:00
k.renner 562b2dcc32 Merge pull request 'feat(k8s-2): bare-metal config for saturn' (#587) from k8s-2-baremetal into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#587
2026-08-25 20:14:04 +00:00
claude-botandClaude Opus 5 ef6111bda3 feat(k8s-2): bare-metal config for saturn
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Structurally identical to k8s-1.yaml; only serials, MACs and addresses differ.
Written from saturn's live hardware rather than assumed: the X710 port 1 MAC
came from the guest that currently owns it, since the host cannot see a
passed-through NIC.

  Kingston  50026B7382DA5F6E  system disk
  Crucial   2332E86B74BC      EPHEMERAL, retiring from osd.0 at 81% wear
  X710 p0   58:47:ca:76:6f:73 trunk, VLAN 40 + VLAN 30
  X710 p1   58:47:ca:76:6f:74 Ceph cluster network, 10.99.0.2/29

Links are addressed by kernel name rather than LinkAliasConfig, and both disks
by serial. Talos and Proxmox do not agree on NVMe enumeration order, and a
network document that fails to resolve takes the whole machine's DHCP with it.

Not applied by anything on merge — talos patches are applied by hand. Landing
it before the conversion so the file under review is the file that gets
installed.

Verified with talosctl validate --mode metal against v1.12.6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 22:12:11 +02:00
k.renner 269ccef5d2 Merge pull request 'chore(rook-ceph): drop k8s-2 from the storage fleet for saturn's conversion' (#586) from ceph-remove-k8s-2 into master
Reviewed-on: k.renner/homelab#586
2026-08-25 20:07:26 +00:00
claude-botandClaude Opus 5 ae6e013877 chore(rook-ceph): drop k8s-2 from the storage fleet for saturn's conversion
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Removing the node from storage.nodes before purging its OSD is what makes the
purge stick. Under useAllNodes the operator rebuilt a purged OSD within one
reconcile and began backfilling onto the drive being replaced; listing nodes
explicitly exists so a node can be taken out first.

Re-add after the conversion naming the device by /dev/disk/by-id, as k8s-1
does. A bare device name is not safe on bare metal: Talos enumerates the three
NVMe drives in a different order than Proxmox, and nvme0n1 is the system disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 22:02:36 +02:00
k.renner 39533bce25 Merge pull request 'docs: runbook for converting a control-plane node to bare metal' (#584) from baremetal-conversion-runbook into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#584
2026-08-25 19:57:02 +00:00
claude-botandClaude Opus 5 5e80a11f15 docs: cite Proxmox's documented quorum recovery
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
pvecm expected 1 is the official recovery, documented verbatim in the storage
replication chapter of the Proxmox admin guide. Its caveat is about restoring
the missing node instead; here the missing node is deliberately gone for good,
so enforcing quorum is the correct branch rather than a workaround.

Also records that it must be run only once the node is alone -- corosync
rejects a value below the members it can see, which is why the attempt on the
current two-node cluster returned CS_ERR_INVALID_PARAM -- and that it is
runtime only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:56:35 +02:00
k.renner 4a2839b35f Merge pull request 'chore: bring immich back up, keep cephfs-metadata degraded until the end' (#585) from restore-after-jupiter into master
Reviewed-on: k.renner/homelab#585
2026-08-25 19:56:10 +00:00
claude-botandClaude Opus 5 5b47dc3271 chore: bring immich back up, keep cephfs-metadata degraded until the end
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
jupiter is converted and its OSD rebuilt, so the capacity constraint that took
immich down is gone with all three nodes back.

cephfs-metadata deliberately stays at size 2 / min_size 1. It has to be
degraded again for saturn and then neptune, and each transition re-replicates
the pool, so toggling it per node buys nothing but churn. Its comment said to
restore it once k8s-1 was back, which is now misleading about the plan; it
restores after the last conversion instead.

The immich comments lose their jupiter-specific framing — taking both
Deployments down is a pre-flight step for any single-node maintenance now, and
the runbook carries it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:53:33 +02:00
claude-botandClaude Opus 5 7c9fa97423 docs: runbook for converting a control-plane node to bare metal
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Written from doing jupiter, with saturn and neptune still to go. The conversion
itself was straightforward; every failure came from state that outlived the VM,
so the runbook is weighted towards those rather than towards the happy path.

The three that cost real time:

A rebuilt node gets a new MAC and a new lease, and Ceph keeps its mon pinned to
the old address in the monmap, the mon-endpoints ConfigMap and the deployment's
--public-addr. The mon then cannot bind, the operator's next step wants to
restart a healthy mon, Ceph refuses because that would drop quorum, and it
retries every minute forever without reaching the storage reconcile. Fixing the
ConfigMap before the monmap and the deployment matters, or the operator
recreates the mon from the stale entry.

Talos stops running default DHCP for the whole machine as soon as one new-style
network document exists, and a LinkAliasConfig selector that matches nothing is
skipped silently. Together those turn a small config mistake into a node with no
address on any interface.

EPHEMERAL placement is honoured only on first provision, so it has to be right
in the first apply-config; afterwards it is ignored and relocating needs a wipe.

Also records the Proxmox quorum step that starts mattering at the second
conversion, the Secure Boot blocker, and that Talos and Proxmox enumerate the
same three NVMe drives in different orders — which is why the OSD is named by
by-id path rather than device name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:49:56 +02:00
k.renner 41e756107a Merge pull request 'feat(rook-ceph): re-add k8s-1, naming the PM983 by by-id path' (#580) from ceph-readd-k8s-1 into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#580
2026-08-25 19:13:28 +00:00
claude-botandClaude Opus 5 ef6558fbb5 feat(rook-ceph): re-add k8s-1, naming the PM983 by by-id path
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
jupiter is back as a bare-metal Talos node, so its OSD returns to the storage
fleet on the new drive.

The device is named by /dev/disk/by-id rather than by bare device name, because
the bare name would be wrong here. Talos enumerates this machine's three NVMe
drives in a different order than Proxmox did, and nvme0n1 on k8s-1 is the
Kingston system disk:

  nvme0n1  50026B7382B48B86  Kingston  Talos system disk
  nvme1n1  2332E86B749A      Crucial   Talos EPHEMERAL (/var)
  nvme2n1  S437NX0M400426    PM983     this OSD

Rook would most likely have declined the system disk for carrying partitions,
but relying on that is luck rather than intent. The by-id path derives from
model and serial and does not depend on enumeration order.

k8s-2 and k8s-3 keep bare names: each guest sees exactly one block device,
the drive passed through from its host, so there is nothing to disambiguate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:12:28 +02:00
k.renner 7804dbe169 Merge pull request 'fix(k8s-1): address links by name, and give the node a rescue path' (#578) from k8s-1-network-fix into master
Reviewed-on: k.renner/homelab#578
2026-08-25 19:11:00 +00:00
claude-botandClaude Opus 5 e4883870b8 fix(k8s-1): address links by kernel name instead of by alias
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
The first install of this node came up with no address on any interface and had
to be recovered from the console. This is the configuration that replaced it,
now running on jupiter.

The alias layer was the cause, but not for the reason it first appeared. Reading
the v1.12.6 source: aliases ARE supported as VLAN parents — link_config.go
resolves parent links through the alias map, and the field's own doc comment
says so. The structure was fine. What is not fine is the failure behaviour.

LinkAliasConfigController skips a selector that matches zero links with no log
of any kind, contradicting the doc comment on the type which claims an error is
returned. And permanent_addr comes from the ETHTOOL_GPERMADDR ioctl with no
fallback to the current address, so a driver that does not answer it yields an
empty string, an unmatched glob, and silence.

That alone would have been survivable. What made it total is that Talos stops
running default DHCP operators for the WHOLE machine as soon as a single
new-style link, VLAN or DHCP document exists — the gate is presence of a
document, not success of resolution. So the silent miss removed the only
configured path while the safety net was already disabled. There is no fail-open
option.

Aliases are worth having and should come back. The condition is that they are
verified rather than assumed: apply with `--mode=try`, which rolls back on its
own, and check that linkaliasspec exists and that permanentAddr is actually
populated before trusting it. A LinkConfig on the real kernel name alongside the
alias resolves regardless of whether the alias landed, and costs nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:10:00 +02:00
k.renner 13a18b3a96 Merge pull request 'feat(k8s-1): put EPHEMERAL on the retired Crucial, not the system disk' (#575) from k8s-1-ephemeral-on-crucial into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#575
2026-08-25 18:33:09 +00:00
claude-botandClaude Opus 5 fe6d232b3a docs(k8s-1): correct the device counts and the second I226's name
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Three NVMe drives are present, not two: the Crucial stays in the machine as
EPHEMERAL rather than being pulled.

The second I226 is enp90s0, not enp89s0. Adding that third drive renumbered
the PCI buses and renamed it, which is a concrete demonstration of why every
NIC in this file is matched by permanent MAC rather than by name or bus path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 20:30:04 +02:00
claude-botandClaude Opus 5 d99628053c feat(k8s-1): put EPHEMERAL on the retired Crucial, not the system disk
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
The Crucial P3 that was this node's Ceph OSD stays in the machine rather than
being pulled, and takes over /var. At 87% of rated endurance it is finished as
an OSD, but everything EPHEMERAL holds — containerd images and snapshots, pod
logs, the Ceph mon store — is reconstructible, so the drive is consumable in
that role.

It also removes the constraint that keeps biting elsewhere: /var becomes
~931 GB instead of the 64 GB the VM-era zvol gave it. k8s-3 tripped
DiskPressure twice today the moment it absorbed another node's pods, with
image GC already at its floor and ~20 GB of unreclaimable overlayfs snapshots.
This node will not have that problem, and the same treatment is available to
saturn and neptune when they convert.

Talos only honours volume configuration when the volume does not yet exist, so
this must ship in the first apply-config; applied later it is silently ignored
and relocating EPHEMERAL then needs a disk wipe and a boot from install media.

The selector matches an exact serial. There are three NVMe drives in this
machine now and `disk.transport == 'nvme' && !system_disk` would match the
PM983 as well, which would hand Talos the OSD drive.

Verified with talosctl validate --mode metal against v1.12.6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 20:15:09 +02:00
11 changed files with 547 additions and 220 deletions
+7 -7
View File
@@ -131,18 +131,18 @@ steps:
- |
# Ensure Talos image exists on all Proxmox nodes via API.
#
# jupiter is NOT in this list: it runs Talos on bare metal and is no
# longer a Proxmox node, so the storage API returns nothing for it and
# the download-wait loop below would burn its full 60x10s before
# giving up. saturn and neptune still host k8s-2/k8s-3 as VMs and
# still need the nocloud image; bare-metal nodes boot the metal-amd64
# image from their own installed disk instead.
# Only nodes still running Proxmox belong in this list. jupiter and
# saturn run Talos on bare metal now, so the storage API returns
# nothing for them and the download-wait loop below would burn its full
# 60x10s per node before giving up. Bare-metal nodes boot the
# metal-amd64 image from their own installed disk instead, and the
# whole step goes away once neptune is converted too.
TALOS_VERSION="v1.12.6"
TALOS_SCHEMATIC="88e3752db032a08082972bd0adf16a80849e3870c3f95cf5b669936c5699e5ad"
TALOS_URL="https://factory.talos.dev/image/$TALOS_SCHEMATIC/$TALOS_VERSION/nocloud-amd64.raw"
TALOS_FILENAME="talos-$TALOS_VERSION-$(echo "$TALOS_SCHEMATIC" | cut -c1-12)-nocloud-amd64.raw"
API_TOKEN="$TF_VAR_proxmox_user!$TF_VAR_proxmox_token_id=$TF_VAR_proxmox_token_secret"
for node in saturn neptune; do
for node in neptune; do
EXISTS=$(curl -sk "$TF_VAR_proxmox_api_url/api2/json/nodes/$node/storage/local/content" \
-H "Authorization: PVEAPIToken=$API_TOKEN" 2>/dev/null | grep -c "$TALOS_FILENAME" || true)
if [ "$EXISTS" -gt 0 ]; then
+340
View File
@@ -0,0 +1,340 @@
# Runbook — converting a control-plane node from Proxmox VM to bare metal
Converts one MS-01 from "Proxmox host running a Talos VM" to "Talos on bare
metal", and swaps its Ceph OSD onto a Samsung PM983 at the same time. Written
after doing jupiter on 2026-08-25; saturn and neptune remain.
Everything here that reads like an over-specific warning is something that
actually happened. The conversion itself is straightforward — the failures were
all in state that outlived the VM.
## What the node ends up as
| | |
|---|---|
| System disk | Kingston OM8PGP41024Q-A0, selected by serial |
| EPHEMERAL (`/var`) | the retired Crucial P3, selected by serial |
| Ceph OSD | Samsung PM983, selected by `/dev/disk/by-id` |
| Management | VLAN 40 tagged on X710 port 0 |
| v30iot | VLAN 30 on the same port, named `ens19` |
| Ceph cluster net | X710 port 1, `10.99.0.x/29`, MTU 9000 |
## Before you start
- [ ] All three OSDs up, all mons in quorum, PGs `active+clean`. Do not start
from a degraded cluster; the window itself removes one of three.
- [ ] Cluster fits on two nodes. Check `kubectl describe node` allocation on the
two survivors. Take immich down (`kubernetes/apps/immich`, both
Deployments to `replicas: 0`) — it frees ~1000m against a shortfall of
roughly 200m and takes its image pulls off the remaining nodes.
- [ ] `cephfs-metadata` to size 2 / min_size 1 for the duration. At size 3 on
two hosts every PG is undersized with min_size 2, so one further OSD loss
blocks IO on every CephFS mount cluster-wide.
- [ ] Talos metal ISO on a USB stick. Schematic
`a21cec8f35ae32ab5e9a452e7b13500ca8776053930b8e182b9cb23eed186b64`
(`net.ifnames=1`, intel-ucode, thunderbolt; no qemu-guest-agent).
Verify by comparing partition offsets and sizes against the ISO with
`fdisk -l` — blkid labels lie after a rewrite.
- [ ] Physical access. Secure Boot must be turned off (see below), and that is
the one step nothing can do remotely.
## 1. Evacuate the OSD
```sh
ceph osd out <id>
```
Wait until no PGs are `backfilling` or `backfill_wait`. Do **not** wait for
`ceph osd safe-to-destroy` — pools at size 3 keep the OSD in their acting set
because three copies cannot be placed on two hosts, so it never returns clean.
Then remove it. The node must already be out of `cephcluster.yaml`'s
`storage.nodes` list, or the operator rebuilds the OSD within one reconcile and
starts backfilling onto the drive you are about to pull:
```sh
kubectl -n rook-ceph scale deployment rook-ceph-osd-<id> --replicas=0
ceph osd purge <id> --yes-i-really-mean-it
kubectl -n rook-ceph delete deployment rook-ceph-osd-<id>
```
## 2. Drain, and leave the cluster
```sh
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --timeout=300s
```
Single-replica workloads with `minAvailable: 1` PDBs will refuse eviction — that
is deliberate, they exist to surface the pod rather than let it be yanked. On
this cluster that is `influxdb-influxdb2-0`, `woodpecker-server` and the
CNPG primary. Move each one deliberately with `kubectl delete pod`; they
reschedule onto the survivors and their RBD volumes follow.
```sh
talosctl -n <node> reset --graceful --wipe-mode system-disk
kubectl delete node <node>
```
`--wipe-mode system-disk` and not the default `all`: the OSD drive is being
pulled anyway and wiping a terabyte of it is wasted time. `--graceful` leaves
etcd cleanly. Delete the Node object — it carries the `unschedulable` flag from
the drain, which the rebuilt node would otherwise inherit.
## 2a. Destroy the VM in OpenTofu — while the host is still a PVE node
Remove `opentofu/vm_15N_k8s_N.tf`, then apply. This **must** happen before the
host leaves the Proxmox cluster: afterwards the provider cannot reach the node,
the plan errors, and the only escape is `tofu state rm`, which is off the table.
Point `proxmox_api_url` at a host that will *survive* the conversion, not the one
being converted.
Manual triggers are currently broken by the Woodpecker v3.18.0 regression
(issue #582). Until that is resolved, run it locally:
```sh
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… AWS_REGION=fsn1 \
tofu -chdir=opentofu init -backend-config=<file>
… plan -target=module.k8s_N -var-file=<file> -out=<plan>
… apply <plan>
```
The `AWS_*` environment variables are required *in addition to*
`-backend-config`, or the S3 backend fails `HeadObject` with 403 even with
credentials that can read the object fine via the aws CLI.
## 2b. Proxmox quorum — matters from the second conversion onward
Two nodes remain after jupiter, with `expected_votes: 2` and quorum 2. Removing
one leaves the last node inquorate and `/etc/pve` read-only, which blocks the
final VM destroy. On the surviving node:
```sh
pvecm expected 1
```
This is the official recovery. Proxmox documents it in the storage replication
chapter (`/usr/share/pve-docs/chapter-pvesr.html`), verbatim:
> If you have no quorum, we strongly advise to fix this first and make the node
> operable again. Only if this is not possible at the moment, you may use the
> following command to enforce quorum on the current node: `# pvecm expected 1`
The caveat there is about restoring the missing node instead. Here the missing
node is deliberately gone for good, so enforcing quorum is the correct branch
rather than a workaround.
Run it only once the node is actually alone — attempting it while both nodes are
up fails with `CS_ERR_INVALID_PARAM`, since corosync will not set expected votes
below the members it can currently see. Which is exactly why it becomes valid
when one remains.
Runtime only; it does not survive a reboot, so re-run it if the survivor
restarts before its own conversion.
## 3. Hardware
Power the host off, then:
1. Kingston boot drive out of slot 1, into slot 2.
2. Crucial P3 out of slot 2 — it becomes EPHEMERAL, so put it in slot 3 rather
than setting it aside.
3. Slot 1 switch to **U.2**. Set it before the drive goes in; there is nothing
to record afterwards, the installed drive type implies the position.
4. PM983 onto the included 7mm M.2-to-U.2 adapter, into slot 1.
Boot the host's existing PVE once before touching Talos and check the drive:
```sh
nvme smart-log /dev/nvmeXn1 | grep -iE 'percentage_used|media_errors|critical'
nvme id-ns /dev/nvmeXn1 -H | grep -i 'in use' # want 512b data, 0 metadata
wipefs /dev/nvmeYn1 # Crucial: clear the old bluestore sig
ip link set <ceph-port> up && ethtool <ceph-port> | grep -i 'link detected'
```
This is the last revert point. A bad drive here means the Crucial goes back and
the VM is recreated from tofu with nothing lost. Once Talos writes to the
Kingston, PVE on that host is gone.
Relocating the boot drive is safe: `rpool` is referenced by NVMe EUI and the
UEFI entries by GPT PARTUUID, neither of which encodes a slot.
## 4. Secure Boot
MS-01 ships with Secure Boot on, and Proxmox satisfies it with a
Microsoft-signed shim. The Image Factory `metal-amd64.iso` is an unsigned UKI
and the firmware rejects it outright. **Turn Secure Boot off in the BIOS.**
A signed variant exists per schematic, but it is signed with SideroLabs' key
which firmware does not trust by default, and the installed system would have to
use the secureboot installer image too. That is a separate project.
## 5. Install
Boot the stick. Maintenance mode DHCPs untagged on every link; on these hosts
that lands on `192.168.8.x`, which is routable from the workstation.
Maintenance mode is effectively write-only — `talosctl get` has no `--insecure`
and `version` returns "API is not implemented in maintenance mode". You cannot
inspect disks or links before applying. Verify serials and MACs from the host's
PVE beforehand, in step 3.
Build the config from the **live cluster's secrets**, not a fresh
`talosctl gen config`, or the node forms a new cluster instead of joining:
```sh
talosctl -n <surviving-node> get mc v1alpha1 -o jsonpath='{.spec}' > cp.yaml
talosctl gen secrets --from-controlplane-config cp.yaml -o secrets.yaml
talosctl gen config homelab https://k8s.renner.uno:6443 \
--with-secrets secrets.yaml --output-types controlplane \
--config-patch @talos/patches/cluster.yaml \
--config-patch @<cilium additive half> \
--config-patch @talos/patches/registry-mirror.yaml \
--config-patch @talos/patches/k8s-N.yaml -o install.yaml
```
Confirm `cluster.id` matches the running cluster before applying.
Strip the generated `HostnameConfig` document — it collides with
`machine.network.hostname` from the per-node patch. `bootstrap.sh` does the same.
`cilium-cni.yaml` cannot be passed whole: its `$patch: delete` document fails
against a base with no `inlineManifests` (issue #581). Use only the additive
second document.
Validate, then apply:
```sh
talosctl validate --config install.yaml --mode metal
talosctl apply-config --insecure -n <maintenance-ip> --file install.yaml
```
## 6. The network config will strand the node if it is wrong
Talos stops running default DHCP operators **for the entire machine** as soon as
a single new-style network document exists — the gate is presence of a document,
not success of resolution. If the configured path fails to materialise there is
no fallback on any interface, and recovery needs a console and install media.
Concretely: do not use `LinkAliasConfig` as a `VLANConfig` parent without
verifying it. Aliases *are* supported (confirmed in v1.12.6 source and by a
maintainer), but a selector matching zero links is skipped **silently**, and
`permanent_addr` comes from an ethtool ioctl with no fallback — if the driver
does not answer, the match is against an empty string and everything downstream
that references the alias does nothing.
Address links by predictable kernel name. To introduce aliases later, do it on a
node that is already up, with `talosctl apply-config --mode=try --timeout=5m`,
which rolls back on its own, and check `talosctl get linkaliasspec`.
Interface names derive from PCI topology and are stable — but not immune.
Adding a third NVMe to jupiter renumbered the buses and renamed the unused
second I226 from `enp89s0` to `enp90s0`.
## 7. EPHEMERAL placement is one-shot
```yaml
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
diskSelector:
match: disk.serial == '<crucial serial>'
minSize: 50GiB
grow: true
```
Talos applies volume configuration **only when the volume does not yet exist**.
Applied afterwards it is silently ignored, and relocating EPHEMERAL then needs a
disk wipe and a fresh boot from install media. It must be in the first
`apply-config`.
Match by serial. `disk.transport == 'nvme' && !system_disk` matches the PM983 as
well, and losing that coin flip formats the OSD drive.
Verify afterwards:
```sh
talosctl -n <node> get volumestatus EPHEMERAL -o yaml | grep -E 'location|phase'
talosctl -n <node> get disks
```
## 8. The mon will not come back on its own
**This is the step that cost the most time on jupiter.** The rebuilt node has a
new MAC and therefore a new DHCP lease, but Ceph has its mon pinned to the old
address in three places. The mon cannot bind an address the host no longer has,
so it crashloops; the operator then tries to restart a *healthy* mon as its next
step, Ceph refuses because that would drop quorum, and it retries every 60
seconds forever without ever reaching the storage reconcile. Nothing else
progresses behind it.
Fix in this order — the order matters, because doing it the other way round has
the operator faithfully recreate the mon from the stale ConfigMap:
```sh
# 1. ConfigMap first
kubectl -n rook-ceph edit cm rook-ceph-mon-endpoints
# remove the dead mon from `data` and from `mapping`
# 2. then the monmap
ceph mon remove <id>
# 3. then the deployment — check it really carries the old address
kubectl -n rook-ceph get deploy rook-ceph-mon-<id> -o yaml | grep public-addr
kubectl -n rook-ceph delete deploy rook-ceph-mon-<id>
```
Rook then creates a **new** mon with the next free ID (jupiter's `c` came back
as `d`), on the correct address. It does not repair the old one.
Note mons use host ports under `network.provider: host`, so a replacement mon
cannot schedule while the broken one's deployment still holds 3300/6789 — the
canary sits `Pending` with "didn't have free ports" until the old deployment is
deleted.
## 9. Re-add the OSD
Add the node back to `cephcluster.yaml` under `storage.nodes`, naming the PM983
by `/dev/disk/by-id`:
```yaml
- name: k8s-N
devices:
- fullpath: /dev/disk/by-id/nvme-SAMSUNG_MZQLB960HAJR-00007_<serial>
```
**Not** a bare device name. Talos enumerates these drives in a different order
than Proxmox did — on jupiter `nvme0n1` is the Kingston *system* disk, and the
PM983 is `nvme2n1`. Rook would probably decline a partitioned system disk, but
that is luck rather than design.
Get the path from `talosctl -n <node> get disk <dev> -o yaml`, under `symlinks`.
## 10. Verify and restore
```sh
kubectl get nodes # Ready
talosctl -n <node> etcd members # 3 members
ceph status # 3 mons in quorum, 3 osds
ceph osd tree # OSD under the right zone/host
ceph device ls # PM983 backing the new OSD
```
Then undo the window changes: `cephfs-metadata` back to size 3, immich back to
1 replica, and `ceph config set osd osd_mclock_profile balanced` if it was
raised to `high_recovery_ops` to speed the evacuation.
The retired Crucial leaves a stale entry in `ceph device ls`. Remove it with
`ceph config-key rm device/<devid>`; the mgr's devicehealth view may lag.
## Known-stale things that will bite
- `bootstrap.sh` cannot generate a config at all right now (issue #581), so a
cold rebuild is broken independently of this work.
- Manual Woodpecker pipelines are broken (issue #582), so anything gated on
`PIPELINE == "..."` must be run another way.
- Cilium's eBPF datapath silently drops VLAN-tagged traffic unless
`bpf.vlanBypass` is set. Both `mgmt0` and `ens19` are VLAN interfaces on a
converted node. Not observed on jupiter, but it is the first place to look if
the v30iot macvlan workloads misbehave there.
+8 -11
View File
@@ -6,17 +6,14 @@ metadata:
labels:
app.kubernetes.io/name: immich-machine-learning
spec:
# TEMPORARY — RESTORE TO 1 ONCE k8s-1 HAS REJOINED.
#
# Down for the jupiter bare-metal conversion. With one of three nodes out,
# the remaining pair have no room: cluster CPU requests exceed two nodes'
# capacity, and k8s-3 runs ~5.8 GiB above the kubelet's eviction threshold,
# so every pod that does not need to be running is one less thing competing
# for both. This one is the best candidate: 500m reserved against a measured
# 2m idle draw, and inference bursts hard enough that lowering the request
# instead would be the wrong trade. Photo processing pauses; nothing else
# depends on it.
replicas: 0
# Scale this and immich-server to 0 for the duration of any single-node
# maintenance — cluster CPU requests exceed what two nodes can hold, and this
# pair frees ~1000m against a shortfall of roughly 200m. Stopping them is the
# right lever rather than lowering their requests: 500m reserved against a
# 2m idle draw looks like waste, but inference bursts hard and the request is
# honest about what it needs when someone uploads photos.
# See RUNBOOK-baremetal-conversion.md.
replicas: 1
strategy:
type: RollingUpdate
selector:
+4 -5
View File
@@ -36,11 +36,10 @@ metadata:
labels:
app.kubernetes.io/name: immich-server
spec:
# TEMPORARY — RESTORE TO 1 ONCE k8s-1 HAS REJOINED. See the note on
# immich-machine-learning; immich is offline for the jupiter conversion.
# Its Postgres cluster is deliberately left running, so nothing needs
# restoring beyond these two replica counts.
replicas: 0
# Scaled to 0 alongside immich-machine-learning during single-node
# maintenance; see the note there. The CNPG Postgres cluster stays running,
# so recovery is just these two replica counts.
replicas: 1
strategy:
type: Recreate
selector:
@@ -167,27 +167,34 @@ spec:
# impossible: the OSD is recreated within a reconcile and starts backfilling
# onto the drive being replaced.
#
# k8s-1 is absent while jupiter's OSD drive is swapped and the node is
# converted to bare metal. RE-ADD IT once the PM983 is in and the node has
# rejoined; the cluster runs on two OSDs until then.
#
# nvme0n1 on k8s-2/k8s-3 is the NVMe drive PCI-passed-through from saturn and
# neptune, and is the only block device those guests can see. Bare-metal k8s-1
# will see two — the OSD drive in MS-01 slot 1 (PCI 01:00.0) and the Talos
# system disk in slot 2 (58:00.0) — so when it is re-added, name its device
# deliberately rather than assuming slot 1 keeps enumerating first. The system
# disk is separately protected: talos/patches/k8s-1.yaml pins the installer to
# it by serial, and Rook skips a disk carrying partitions.
# neptune, and is the only block device those guests can see, so a bare device
# name is unambiguous there.
#
# k8s-1 is bare metal and sees three NVMe drives, so it names its OSD by
# /dev/disk/by-id path instead. A bare name would be actively wrong: Talos
# enumerates this machine's drives in a different order than Proxmox did, and
# nvme0n1 here is the Kingston SYSTEM disk. Rook would probably decline it for
# carrying partitions, but that is luck rather than intent. The by-id path is
# derived from model and serial and does not depend on enumeration at all.
#
# nvme0n1 50026B7382B48B86 Kingston — Talos system disk
# nvme1n1 2332E86B749A Crucial — Talos EPHEMERAL (/var)
# nvme2n1 S437NX0M400426 PM983 — this OSD
storage:
useAllNodes: false
useAllDevices: false
nodes:
- name: k8s-2
devices:
- name: nvme0n1
# k8s-2 is absent while saturn's OSD drive is swapped and the node is
# converted to bare metal. RE-ADD IT once the PM983 is in and the node has
# rejoined, naming the device by /dev/disk/by-id as k8s-1 does — bare
# metal sees three NVMe drives and the bare name is not the OSD.
- name: k8s-3
devices:
- name: nvme0n1
- name: k8s-1
devices:
- fullpath: /dev/disk/by-id/nvme-SAMSUNG_MZQLB960HAJR-00007_S437NX0M400426
config:
osdsPerDevice: "1"
# Storage layer must never be evicted by kubelet under node pressure
@@ -87,7 +87,10 @@ metadata:
name: cephfs
namespace: rook-ceph
spec:
# TEMPORARY — REVERT TO size 3 / no min_size OVERRIDE ONCE k8s-1 IS BACK.
# TEMPORARY — RESTORE TO size 3 / no min_size OVERRIDE ONCE SATURN AND
# NEPTUNE HAVE BOTH BEEN CONVERTED TO BARE METAL. It stays at 2/1 across all
# three conversions rather than being toggled per node: each change
# re-replicates the pool, and the window reopens with the next node anyway.
#
# The standing trade for this pool is size 3: it holds ~253 MiB against the
# data pool's 280 GiB, so it contributes nothing measurable to drive wear —
@@ -96,13 +99,14 @@ spec:
# quarter of a gigabyte to keep that intact is the right trade in steady
# state.
#
# It does not hold while one of three hosts is out for the jupiter bare-metal
# conversion. size 3 on two remaining hosts leaves every PG undersized at two
# copies with min_size 2, so a single further OSD loss drops the pool below
# min_size and blocks IO on every CephFS mount cluster-wide. At 2/1 the same
# two copies are active+clean instead, and a further loss degrades rather
# than blocks — availability bought at the cost of accepting writes onto a
# single copy while degraded.
# It does not hold while one of three hosts is out. size 3 on two remaining
# hosts leaves every PG undersized at two copies with min_size 2, so a single
# further OSD loss drops the pool below min_size and blocks IO on every
# CephFS mount cluster-wide. At 2/1 the same two copies are active+clean
# instead, and a further loss degrades rather than blocks — availability
# bought at the cost of accepting writes onto a single copy while degraded.
#
# See RUNBOOK-baremetal-conversion.md.
metadataPool:
replicated:
size: 2
+62 -34
View File
@@ -1,7 +1,7 @@
# k8s-1 runs Talos directly on jupiter's MS-01 hardware; k8s-2 and k8s-3 are
# still Proxmox VMs. Every machine-specific selector below differs from theirs
# for that reason: there are no virtio NICs, no PCI-passthrough layer, and both
# NVMe drives are visible to Talos at once.
# for that reason: there are no virtio NICs, no PCI-passthrough layer, and all
# four physical NICs and all three NVMe drives are visible to Talos at once.
#
# Networking uses the v1.12 multi-document config rather than the deprecated
# machine.network.interfaces. That is deliberate: the docs do not define what
@@ -9,9 +9,6 @@
# every link in one schema instead of straddling them.
machine:
network:
# Shared control-plane VIP 10.2.40.60 is configured as a Layer2VIPConfig
# below rather than machine.network.interfaces[].vip. Exposed via UniFi DNS
# as k8s.renner.uno.
hostname: k8s-1
sysctls:
# ens19 (VLAN 30, v30iot) is LINK ONLY — no host addressing. It exists as
@@ -24,10 +21,12 @@ machine:
# This disables v6 entirely on that link, including link-local.
net.ipv6.conf.ens19.disable_ipv6: "1"
install:
# Both NVMe drives are now ordinary block devices to Talos: the Kingston
# OM8PGP41024Q-A0 system disk and the Samsung PM983 that Rook claims as the
# Ceph OSD. Selecting the installer target by serial means it cannot land
# on the OSD drive regardless of which one the kernel enumerates first.
# Three NVMe drives are ordinary block devices to Talos here: the Kingston
# OM8PGP41024Q-A0 system disk, the Samsung PM983 that Rook claims as the
# Ceph OSD, and the retired Crucial P3 that carries EPHEMERAL (see the
# VolumeConfig below). Every one of them is selected by serial. Nothing in
# this file may use a broad selector: `disk.transport == 'nvme'` matches all
# three, and the failure mode is Talos taking the OSD drive.
diskSelector:
serial: 50026B7382B48B86
certSANs:
@@ -37,40 +36,70 @@ machine:
cluster:
allowSchedulingOnControlPlanes: true
---
# Physical NICs are aliased by permanent MAC. Both X710 ports bind i40e, so the
# driver selector k8s-2/k8s-3 use for their single passed-through port would
# match two links here. MAC matching is unambiguous and survives udev renaming.
# EPHEMERAL (/var) goes on the retired Crucial P3, not the system disk.
#
# That drive is at 87% of its rated endurance and was this node's Ceph OSD until
# the PM983 replaced it. /var is exactly the right role for a disk in that
# state: containerd's image and snapshot store, pod logs and the Ceph mon store
# are all reconstructible, so the drive is consumable here in a way it is not as
# an OSD. It also gives /var ~931 GB instead of the 64 GB the VM-era zvol had,
# which is the constraint that makes k8s-2 and k8s-3 trip DiskPressure whenever
# they absorb another node's pods.
#
# THIS ONLY TAKES EFFECT ON FIRST PROVISION. Talos applies volume configuration
# solely when the volume does not yet exist; a config applied after EPHEMERAL
# has been created elsewhere is silently ignored, and relocating it then
# requires wiping the disk and booting install media again.
#
# Matched by serial deliberately. `disk.transport == 'nvme' && !system_disk`
# would match the PM983 as well, and losing that coin flip means Talos formats
# the OSD drive.
apiVersion: v1alpha1
kind: LinkAliasConfig
name: lan0
selector:
match: glob("58:47:ca:74:70:27", mac(link.permanent_addr))
kind: VolumeConfig
name: EPHEMERAL
provisioning:
diskSelector:
match: disk.serial == '2332E86B749A'
minSize: 50GiB
grow: true
---
apiVersion: v1alpha1
kind: LinkAliasConfig
name: ceph0
selector:
match: glob("58:47:ca:74:70:28", mac(link.permanent_addr))
---
# lan0 is the switch trunk (X710 port 0). It carries no addressing of its own;
# VLAN 40 and VLAN 30 ride on top. Proxmox's vlan-aware vmbr0 did this tagging
# before, which is why the VM saw two already-untagged virtio NICs instead.
# X710 port 0 carries the switch trunk. It takes no addressing itself — the
# port's untagged VLAN is the 192.168.8.0/24 network, which this node has no
# business being on — so management and v30iot both ride it as tagged VLANs.
#
# Links are addressed by predictable kernel name. Those derive from PCI
# topology, not probe order — though not immune: adding the third NVMe
# renumbered the buses and moved the unused second I226 from enp89s0 to
# enp90s0. Neither name used here was affected.
#
# LinkAliasConfig would be the better way to pin these, and is worth
# reintroducing — but only once it is proven. Aliasing both X710 ports by
# permanent MAC and using the aliases as VLAN parents produced a node with no
# address on any interface: the aliases were never created, so every document
# referencing them did nothing, including the static address on port 1. Verify
# any alias-based revision with `talosctl apply-config --mode=try`, which rolls
# back on its own, before making it permanent.
apiVersion: v1alpha1
kind: LinkConfig
name: lan0
name: enp2s0f0np0
up: true
---
# Management, VLAN 40. Carries the node IP, etcd, the API and Ceph's public
# network. The k8s-1 VM reached 10.2.40.174 over this same physical port via
# Proxmox's v40srv VNet (vmbr0.40), so the switch trunks VLAN 40 here.
apiVersion: v1alpha1
kind: VLANConfig
name: mgmt0
vlanID: 40
parent: lan0
parent: enp2s0f0np0
up: true
---
apiVersion: v1alpha1
kind: DHCPv4Config
name: mgmt0
---
# Shared control-plane VIP, etcd-elected. Exposed via UniFi DNS as
# k8s.renner.uno.
apiVersion: v1alpha1
kind: Layer2VIPConfig
name: 10.2.40.60
@@ -80,21 +109,20 @@ link: mgmt0
# `master: ens19`, and k8s-2/k8s-3 still present the VLAN as a virtio NIC of
# that name. VLANConfig.name is free-form, so keeping it lets one set of NADs
# serve both VM and bare-metal nodes for as long as the cluster is mixed.
# Nothing else on this machine is called ens19: the physical NICs are
# enp2s0f0np0, enp2s0f1np1, enp87s0 and enp89s0.
apiVersion: v1alpha1
kind: VLANConfig
name: ens19
vlanID: 30
parent: lan0
parent: enp2s0f0np0
up: true
---
# Dedicated Ceph cluster network, VLAN 49 / 10.99.0.0/29, untagged on the wire
# on X710 port 1. Used by Rook for OSD<->OSD replication via
# CephCluster.spec.network.addressRanges.cluster. No default route.
# Dedicated Ceph cluster network on X710 port 1, VLAN 49 / 10.99.0.0/29,
# untagged on the wire. Used by Rook for OSD<->OSD replication via
# CephCluster.spec.network.addressRanges.cluster. Not routed off its own
# segment, and deliberately has no default route.
apiVersion: v1alpha1
kind: LinkConfig
name: ceph0
name: enp2s0f1np1
up: true
mtu: 9000
addresses:
+87 -19
View File
@@ -1,31 +1,99 @@
# k8s-2 runs Talos directly on saturn's MS-01 hardware. Structurally identical
# to k8s-1.yaml — see that file for the reasoning behind each choice; only the
# serials, MACs and addresses differ. k8s-3 is still a Proxmox VM and keeps the
# VM-era layout until neptune is converted.
machine:
network:
hostname: k8s-2
# Shared control-plane VIP — see k8s-1.yaml for the busPath rationale.
interfaces:
- deviceSelector:
busPath: "0000:06:12.0"
dhcp: true
vip:
ip: 10.2.40.60
# v30iot: link only, no host addressing — see k8s-1.yaml.
- deviceSelector:
busPath: "0000:06:13.0"
dhcp: false
# See k8s-1.yaml for context on the dedicated Ceph cluster network.
- deviceSelector:
driver: i40e
addresses:
- 10.99.0.2/29
mtu: 9000
sysctls:
# v30iot NIC carries no host addressing — see k8s-1.yaml.
# ens19 (VLAN 30, v30iot) is LINK ONLY — no host addressing. It is the
# macvlan parent for the v30iot pod attachments; host addresses on that L2
# make node replies bypass the UDM and break every UDM-DNATed flow.
net.ipv6.conf.ens19.disable_ipv6: "1"
install:
disk: /dev/vda
# Three NVMe drives are visible on bare metal. Select the installer target
# by serial so it cannot land on the OSD drive or on EPHEMERAL, whatever
# order the kernel enumerates them in — Talos and Proxmox do not agree on
# that order.
diskSelector:
serial: 50026B7382DA5F6E
certSANs:
- k8s-2.srv.renner.uno
nodeLabels:
topology.kubernetes.io/zone: saturn
cluster:
allowSchedulingOnControlPlanes: true
---
# EPHEMERAL (/var) goes on the retired Crucial P3, not the system disk. That
# drive is at 81% of its rated endurance and is finished as an OSD, but
# everything EPHEMERAL holds is reconstructible, so it is consumable in this
# role. It also gives /var ~931 GB instead of the 64 GB the VM-era zvol had.
#
# ONLY TAKES EFFECT ON FIRST PROVISION. Talos applies volume configuration
# solely when the volume does not yet exist; applied later it is silently
# ignored and relocating needs a disk wipe and a boot from install media.
#
# Matched by serial deliberately: a broad selector such as
# `disk.transport == 'nvme' && !system_disk` would match the PM983 as well.
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
diskSelector:
match: disk.serial == '2332E86B74BC'
minSize: 50GiB
grow: true
---
# X710 port 0 carries the switch trunk and takes no addressing itself — the
# port's untagged VLAN is not one this node belongs on. Management and v30iot
# both ride it as tagged VLANs.
#
# Addressed by predictable kernel name, not by LinkAliasConfig. Aliases are
# valid VLAN parents, but a selector matching zero links is skipped silently
# and Talos disables default DHCP machine-wide as soon as any new-style network
# document exists — so a silent miss leaves the node with no address on any
# interface. See RUNBOOK-baremetal-conversion.md.
apiVersion: v1alpha1
kind: LinkConfig
name: enp2s0f0np0
up: true
---
# Management, VLAN 40. Node IP, etcd, the API and Ceph's public network.
apiVersion: v1alpha1
kind: VLANConfig
name: mgmt0
vlanID: 40
parent: enp2s0f0np0
up: true
---
apiVersion: v1alpha1
kind: DHCPv4Config
name: mgmt0
---
# Shared control-plane VIP, etcd-elected. Exposed via UniFi DNS as
# k8s.renner.uno.
apiVersion: v1alpha1
kind: Layer2VIPConfig
name: 10.2.40.60
link: mgmt0
---
# Named ens19 on purpose: the four v30iot macvlan NADs hard-code
# `master: ens19`, and k8s-3 still presents the VLAN as a virtio NIC of that
# name. VLANConfig.name is free-form, so one set of NADs serves both.
apiVersion: v1alpha1
kind: VLANConfig
name: ens19
vlanID: 30
parent: enp2s0f0np0
up: true
---
# Dedicated Ceph cluster network on X710 port 1, VLAN 49 / 10.99.0.0/29,
# untagged on the wire. OSD<->OSD replication only. Not routed off its own
# segment and deliberately has no default route.
apiVersion: v1alpha1
kind: LinkConfig
name: enp2s0f1np1
up: true
mtu: 9000
addresses:
- address: 10.99.0.2/29
+7 -2
View File
@@ -47,8 +47,13 @@ variable "tags" {
}
variable "template_name" {
type = string
default = "ubuntu-24-04-template"
type = string
# No default clone source. The ubuntu-24.04-template it used to name was
# deleted with saturn's conversion to bare metal, and Proxmox is being
# retired, so a default pointing at a template that does not exist would
# only fail at apply time. Set it explicitly if a clone source is ever
# wanted again.
default = null
}
variable "full_clone" {
-50
View File
@@ -1,50 +0,0 @@
module "k8s_2" {
source = "./modules/proxmox_vm"
name = "k8s-2.srv.renner.uno"
target_node = "saturn"
vmid = 151
memory = 49152
cores = 8
template_name = null
cloud_init = false
full_clone = false
bios = "ovmf"
machine = "q35"
boot = "order=virtio0"
agent_enabled = true
# Second NIC on v30iot so Cilium L2 announce can win ARP on the IoT
# VLAN from this node (previously only k8s-edge had v30iot, so any
# IoT-pool LB lease landing here was unreachable from devices).
extra_network_bridges = ["v30iot"]
disks = [
{
storage = "local-zfs"
size = "64G"
format = "raw"
slot = "virtio0"
file_id = local.talos_image
}
]
hostpci = [
{
mapping = "osd-ssd"
},
{
mapping = "x710-port1"
}
]
tags = "kubernetes;talos;controlplane"
ssh_public_keys = local.common_ssh_keys
vm_state = "running"
# Rebuild pipeline issues `-replace`; skip graceful shutdown so a
# non-responsive Talos guest can't hang the destroy for 30 minutes.
stop_on_destroy = true
}
-71
View File
@@ -1,71 +0,0 @@
resource "proxmox_virtual_environment_vm" "ubuntu_24_04_template" {
name = "ubuntu-24.04-template"
node_name = "saturn"
vm_id = 605
started = false
template = true
on_boot = false
keyboard_layout = "en-us"
bios = "seabios"
agent {
enabled = true
}
cpu {
cores = 2
type = "x86-64-v3"
}
memory {
dedicated = 2048
}
initialization {
datastore_id = "local-zfs"
ip_config {
ipv4 {
address = "dhcp"
}
}
user_account {
username = "ladmin"
keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPzFNBP3bq2m/6JqDqod9w+FP/uxXxHMolqhq3Ojflr1 krenner@fortress"
]
}
}
disk {
datastore_id = "local-zfs"
interface = "scsi0"
size = 32
file_format = "raw"
iothread = false
}
network_device {
bridge = "v40srv"
model = "virtio"
}
serial_device {}
scsi_hardware = "virtio-scsi-pci"
lifecycle {
ignore_changes = [
node_name,
started,
initialization,
disk,
cpu,
memory
]
}
}