Files
nix-config-ryan4yin/lib/genMicrovmGuestModule.nix
T
ryan4yin | 二花 90bbe8bc4f fix(monitoring): stop recurring false alerts (#395)
The Telegram channel was dominated by alerts that can never be actionable:

- DriveTemperatureHigh/Critical did not filter temperature_type, so
  max(smartctl_device_temperature) picked the drive's rated
  op_limit_max/critical_limit_max instead of the actual reading. ai reported
  95 C and youko 130 C while the drives actually ran at 40-48 C.
- HostPhysicalComponentTooHot excluded label!="tctl", but the AMD k10temp
  hwmon sensor labels itself Tctl, so the CPU control temperature (up to
  ~100 C under load) tripped the alert on every AMD host.
- MicroVM guests share the host's read-only /nix/store, so their
  nix-gc.service always failed and tripped HostSystemdServiceCrashed on all
  six k3s guests. Disable automatic GC in the guest; the host's nix.gc
  reclaims the shared store.

The remaining k3s alert noise comes from workloads running on the masters
that belong on the workers; that is fixed in k8s-gitops, not by resizing
the control-plane VMs.
2026-10-07 12:51:30 +08:00

144 lines
4.1 KiB
Nix

{
pkgs,
hostName,
networking,
# vCPU count and RAM (MiB) for the QEMU guest
vcpu,
mem,
# persistent volume sizes in MiB
etcSize ? 64,
varSize ? 4096,
homeSize ? 4096,
# extra stateful mounts (e.g. aquamarine's /data on the passed-through HDDs)
extraVolumes ? [ ],
...
}:
let
inherit (networking) proxyGateway proxyGateway6;
inherit (networking.hostsAddr.${hostName}) ipv4;
ipv4WithMask = "${ipv4}/24";
# deterministic MAC per guest so the tap is stable and the guest can match its
# NIC by MAC (the qemu NIC name inside the guest is not enp2s0)
octets = builtins.match "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)" ipv4;
toHex =
n:
let
h = pkgs.lib.toHexString (builtins.fromJSON n);
in
if builtins.stringLength h == 1 then "0${h}" else h;
mac = "02:00:00:00:${toHex (builtins.elemAt octets 2)}:${toHex (builtins.elemAt octets 3)}";
# tap iface name on the host (IFNAMSIZ is 16, so <= 15 chars). All homelab VMs
# are 192.168.5.0/24, so the last octet is unique per host.
tapId = "vm${builtins.elemAt octets 3}";
in
{
# The guest is a MicroVM: the root is in RAM, /nix/store is the host's
# (read-only, via virtiofs) so no OS image has to be built or uploaded --
# everything here comes from this flake. Only the *stateful* paths below are
# on real volumes and therefore survive a reboot.
microvm = {
hypervisor = "qemu";
inherit vcpu mem;
socket = "control.socket";
interfaces = [
{
type = "tap";
id = tapId;
inherit mac;
}
];
shares = [
{
tag = "ro-store";
proto = "virtiofs";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
volumes = [
{
mountPoint = "/etc"; # ssh host keys (the agenix age identity!), machine-id
image = "etc.img";
size = etcSize;
}
{
mountPoint = "/var"; # k3s data (/var/lib/rancher/k3s), nixos uid/gid maps
image = "var.img";
size = varSize;
}
{
mountPoint = "/home"; # user data
image = "home.img";
size = homeSize;
}
]
++ extraVolumes;
};
networking = {
inherit hostName;
networkmanager.enable = false;
useDHCP = false;
# Keep node IPv6 addresses stable for Cilium across agent restarts.
tempAddresses = "disabled";
};
networking.useNetworkd = true;
systemd.network.enable = true;
# match the virtio NIC by MAC: qemu names it something like enp0s1, not enp2s0
systemd.network.networks."10-${hostName}" = {
matchConfig.MACAddress = [ mac ];
networkConfig = {
Address = [ ipv4WithMask ];
DNS = [ proxyGateway ];
DHCP = "ipv6";
IPv6AcceptRA = true;
IPv6PrivacyExtensions = false;
LinkLocalAddressing = "ipv6";
};
# Keep the static resolver below as the only DNS server; router advertisements and DHCPv6
# still provide addresses and routes but must not add their own resolver entries.
dhcpV6Config.UseDNS = false;
ipv6AcceptRAConfig.UseDNS = false;
routes = [
{
Destination = "0.0.0.0/0";
Gateway = proxyGateway;
}
{
Destination = "::/0";
Gateway = proxyGateway6;
GatewayOnLink = true;
}
];
linkConfig.RequiredForOnline = "routable";
};
# journald on tmpfs -- keep it small
services.journald.settings.Journal = {
SystemMaxUse = "512M";
RuntimeMaxUse = "256M";
};
system.stateVersion = "26.05";
# The guest shares the host's read-only /nix/store (virtiofs) and has no store
# of its own, so its nix-gc.service can never succeed; it just failed on every
# k3s guest and tripped HostSystemdServiceCrashed. The host's own nix.gc
# reclaims the shared store, so disable automatic GC inside the guest.
nix.gc.automatic = false;
# MicroVM guests share the host's store and have no physical hardware to
# inspect, nothing to trace, no persisted /var/cache for a `locate` index,
# and nothing to advertise over mDNS.
modules.hardwareTools.enable = false;
modules.debugTools.enable = false;
modules.locate.enable = false;
modules.mdns.enable = false;
}