Compare commits

...
Author SHA1 Message Date
issue-fix-bot cd95c39765 fix: [alert] TargetDown (pve) (#579)
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
2026-08-25 19:45:14 +00: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
4 changed files with 72 additions and 61 deletions
@@ -108,12 +108,16 @@ spec:
# identical `id`, differing `instance` — a real TSDB cardinality
# cost that dashboards had to paper over with max by (id).
#
# Target the pve Service (apps ns) rather than a hardcoded node IP:
# its EndpointSlice load-balances the PVE API across all three nodes,
# so cluster metrics keep flowing as long as ANY node is up, instead
# of pausing whenever .201 (jupiter) specifically is down. The
# exporter reaches the API on 8006 (verify_ssl:false, so the LB'd
# Target the pve Service (apps ns) rather than a hardcoded node IP,
# so this job does not have to be edited when a node is renumbered.
# The exporter reaches the API on 8006 (verify_ssl:false, so the LB'd
# cert hostname mismatch is a non-issue).
#
# This is round robin, NOT failover: the EndpointSlice is hand-written
# with `ready: true` asserted and nothing health-checks it. One scrape
# makes several API calls, so a single down member fails 100% of this
# job's scrapes rather than 1/N of them (issue #579). Keeping the
# EndpointSlice free of decommissioned nodes is what keeps this job up.
- job_name: pve
static_configs:
- targets:
@@ -135,12 +139,12 @@ spec:
# scraped — this is the split recommended by prometheus-pve-exporter
# upstream to avoid the cluster-collector duplication above.
# Targets are the node FQDNs, not raw IPs: the instance label then
# reads jupiter/neptune/saturn.renner.uno, and a renumbered node is
# picked up from DNS without editing this file.
# reads neptune/saturn.renner.uno, and a renumbered node is picked up
# from DNS without editing this file. jupiter is absent on purpose --
# its hardware runs Talos as k8s-1 now and has no PVE API to scrape.
- job_name: pve-node
static_configs:
- targets:
- "jupiter.renner.uno"
- "neptune.renner.uno"
- "saturn.renner.uno"
metrics_path: /pve
+12 -7
View File
@@ -1,7 +1,17 @@
# PVE node IPs are hardcoded here despite the general "IPs aren't static"
# rule for the homelab: corosync requires stable cluster member identity,
# so jupiter/neptune/saturn keep .201/.202/.203 across reboots and PVE
# upgrades. If a node is permanently renumbered, edit this file.
# so neptune/saturn keep .202/.203 across reboots and PVE upgrades. If a node
# is permanently renumbered, edit this file.
#
# There are no health checks on these entries -- `ready: true` is asserted, not
# observed -- so a member listed here that is down still takes its share of the
# round robin. A decommissioned node must be REMOVED, not left in rotation:
# the monitoring `pve` job makes several API calls per scrape, and any one of
# them landing on a dead member fails the whole scrape (TargetDown, issue #579).
#
# jupiter (.201) was removed for exactly that reason: its MS-01 hardware now
# runs Talos directly as k8s-1 and it is no longer a Proxmox node. See
# kubernetes/talos/patches/k8s-1.yaml and .woodpecker/deploy-infrastructure.yml.
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
@@ -16,11 +26,6 @@ ports:
port: 8006
protocol: TCP
endpoints:
- addresses:
- 10.2.40.201
hostname: jupiter
conditions:
ready: true
- addresses:
- 10.2.40.202
hostname: neptune
@@ -167,17 +167,20 @@ 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
@@ -188,6 +191,9 @@ spec:
- 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
+32 -36
View File
@@ -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
@@ -26,7 +23,7 @@ machine:
install:
# 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 now carries EPHEMERAL (see 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.
@@ -51,9 +48,8 @@ cluster:
#
# 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 on the system disk is silently ignored, and relocating it
# then requires wiping the disk and booting install media again. So this
# document has to be present in the very first `apply-config --insecure`.
# 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
@@ -67,42 +63,43 @@ provisioning:
minSize: 50GiB
grow: 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 —
# which is not hypothetical: adding the third NVMe renumbered the PCI buses and
# moved the second I226 from enp89s0 to enp90s0.
apiVersion: v1alpha1
kind: LinkAliasConfig
name: lan0
selector:
match: glob("58:47:ca:74:70:27", mac(link.permanent_addr))
---
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
@@ -112,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 enp90s0.
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: