Compare commits

...
Author SHA1 Message Date
issue-fix-bot a7ccbd7a76 fix: [alert] TargetDown (pve-node) (#573)
ci/woodpecker/pr/ci-manifests-test Pipeline is pending
ci/woodpecker/pr/ci-scripts-test Pipeline is pending
ci/woodpecker/pr/pr-review Pipeline is pending
2026-08-25 18:48:48 +00: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
k.renner 63fc1ce63e Merge pull request 'chore(immich): take server and machine-learning down for the jupiter window' (#570) from immich-window-scaledown into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#570
2026-08-25 15:36:48 +00:00
claude-botandClaude Opus 5 6c1f34e992 chore(immich): take server and machine-learning down for the jupiter window
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
With one of three nodes out for the bare-metal conversion, the remaining pair
cannot hold the workload: cluster CPU requests exceed two nodes' capacity by
roughly 200m once DaemonSets and unplaceable Ceph pods are discounted, and
k8s-3 sits ~5.8 GiB above the kubelet's eviction threshold with image GC
already at its floor.

immich is the right thing to stop rather than squeeze. machine-learning
reserves 500m against a 2m idle draw, but inference bursts hard, so cutting
its request would misrepresent what it needs the moment someone uploads
photos. Stopping it removes the reservation honestly, and takes its pods and
image pulls off k8s-3 for the duration.

The Postgres cluster stays up, so recovery is these two replica counts and
nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 17:36:09 +02:00
k.renner 9e9fdf44bd Merge pull request 'fix(rook-ceph): list OSD nodes explicitly and drop k8s-1' (#568) from ceph-exclude-k8s-1 into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#568
2026-08-25 15:25:40 +00:00
claude-botandClaude Opus 5 b85107b95b fix(rook-ceph): list OSD nodes explicitly and drop k8s-1
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
useAllNodes makes it impossible to retire an OSD ahead of pulling its drive.
Purging osd.2 and deleting its deployment held for one reconcile: the operator
saw a node it was told to use, carrying a device with a BlueStore signature,
and rebuilt the OSD. Ceph then began backfilling onto the very drive being
replaced.

Listing nodes explicitly makes membership of the storage fleet a property of
this file rather than of which nodes happen to exist, so a node can be removed
before its disk is touched.

k8s-1 is out while jupiter's OSD is swapped for the PM983 and the node is
converted to bare metal. The cluster runs on two OSDs until it is re-added;
all pools are size 2 / min_size 1 and stay fully redundant across the pair.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 17:24:47 +02:00
k.renner 2038cd34ae Merge pull request 'fix(crowdsec): request 50m CPU instead of the chart's 500m' (#567) from crowdsec-cpu-requests into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#567
2026-08-25 15:07:22 +00:00
5 changed files with 101 additions and 22 deletions
+11 -1
View File
@@ -6,7 +6,17 @@ metadata:
labels:
app.kubernetes.io/name: immich-machine-learning
spec:
replicas: 1
# 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
strategy:
type: RollingUpdate
selector:
+5 -1
View File
@@ -36,7 +36,11 @@ metadata:
labels:
app.kubernetes.io/name: immich-server
spec:
replicas: 1
# 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
strategy:
type: Recreate
selector:
+19 -1
View File
@@ -16,11 +16,29 @@ ports:
port: 8006
protocol: TCP
endpoints:
# jupiter is OUT OF SERVICE as a PVE node: its MS-01 hardware is being
# converted to run Talos directly as k8s-1 (see
# kubernetes/talos/patches/k8s-1.yaml), and nothing answers the PVE API on
# .201 while that is in progress — verified 2026-08-25 from pve-exporter,
# which fails with "[Errno 113] Host is unreachable" for .201.
#
# This slice is manually managed, and Kubernetes health-checks nothing it did
# not create — a hardcoded `ready: true` keeps kube-proxy and Traefik load
# balancing onto a dead address forever. That silently defeated the whole
# point of pointing the pve scrape job at this Service rather than at a node
# IP (see the job_name: pve comment in
# kubernetes/apps/monitoring/helmrelease-prometheus.yaml): cluster metrics
# were meant to survive any one node being down, but instead
# up{job="pve"} flapped 0/1 as connections landed on .201, and pve.renner.uno
# served an error for the same fraction of requests.
#
# Marking it not-ready is the supported way to drain a manual endpoint. If
# jupiter is ever rebuilt as a PVE member, flip this back to true.
- addresses:
- 10.2.40.201
hostname: jupiter
conditions:
ready: true
ready: false
- addresses:
- 10.2.40.202
hostname: neptune
@@ -160,19 +160,34 @@ spec:
- "10.2.40.0/24"
cluster:
- "10.99.0.0/29"
# Nodes are listed explicitly rather than useAllNodes, so that a node can be
# taken out of the storage fleet by removing it here. Under useAllNodes the
# operator re-provisions an OSD on any node whose device still carries a
# BlueStore signature, which makes purging an OSD ahead of pulling its drive
# 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.
storage:
useAllNodes: true
useAllNodes: false
useAllDevices: false
devices:
# On k8s-2/k8s-3 this is the NVMe drive PCI-passed-through from saturn
# and neptune, and it is the only block device the guest can see. k8s-1
# is bare metal on jupiter and sees two: the OSD drive in MS-01 slot 1
# (PCI 01:00.0) and the Talos system disk in slot 2 (58:00.0). Slot 1
# enumerates first, so nvme0n1 is still the OSD drive there — but that
# is enumeration order, not a guarantee. The system disk is protected
# from the other direction instead: talos/patches/k8s-1.yaml pins the
# installer to it by serial, and Rook skips a disk carrying partitions.
- name: nvme0n1
nodes:
- name: k8s-2
devices:
- name: nvme0n1
- name: k8s-3
devices:
- name: nvme0n1
config:
osdsPerDevice: "1"
# Storage layer must never be evicted by kubelet under node pressure
+40 -8
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
@@ -24,10 +24,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 now 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,9 +39,39 @@ machine:
cluster:
allowSchedulingOnControlPlanes: true
---
# 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 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`.
#
# 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: VolumeConfig
name: EPHEMERAL
provisioning:
diskSelector:
match: disk.serial == '2332E86B749A'
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.
# 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
@@ -81,7 +113,7 @@ link: mgmt0
# 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.
# enp2s0f0np0, enp2s0f1np1, enp87s0 and enp90s0.
apiVersion: v1alpha1
kind: VLANConfig
name: ens19