--- - name: Install baseline packages (guest agent, Longhorn/iscsi/nfs prereqs) ansible.builtin.apt: name: "{{ node_baseline_packages }}" state: present update_cache: true cache_valid_time: 3600 - name: Persist kernel modules for k3s/Cilium/Longhorn ansible.builtin.copy: dest: /etc/modules-load.d/k3s-cilium-longhorn.conf owner: root group: root mode: "0644" content: "{{ node_baseline_kernel_modules | join('\n') }}\n" - name: Load kernel modules now community.general.modprobe: name: "{{ item }}" state: present loop: "{{ node_baseline_kernel_modules }}" - name: Install sysctl tuning for k3s/Cilium ansible.builtin.copy: dest: /etc/sysctl.d/99-k3s-cilium.conf owner: root group: root mode: "0644" content: | net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 fs.inotify.max_user_instances = 8192 fs.inotify.max_user_watches = 524288 notify: Reload sysctl - name: Enable and start iscsid (Longhorn prerequisite) ansible.builtin.systemd: name: iscsid enabled: true state: started # qemu-guest-agent binds to the virtio-serial channel /dev/virtio-ports/org.qemu.guest_agent.0, # which Proxmox attaches only when the VM's `agent` option is enabled. Our tofu keeps agent # DISABLED on purpose (enabling it makes `tofu apply` block up to 15m waiting for an agent the # stock Debian cloud image hasn't installed yet — bpg gotcha). So we ENABLE the unit for the day # a channel exists, but only START it when the channel is actually present — never fail the # baseline over a missing hypervisor channel. - name: Check whether the qemu guest-agent virtio channel is attached ansible.builtin.stat: path: /dev/virtio-ports/org.qemu.guest_agent.0 register: node_baseline_qga_channel - name: Enable qemu-guest-agent (start only when its virtio channel is present) ansible.builtin.systemd: name: qemu-guest-agent enabled: true state: "{{ 'started' if node_baseline_qga_channel.stat.exists else 'stopped' }}" - name: Create ext4 filesystem on the Longhorn data disk (data-safe; skips if present) community.general.filesystem: fstype: ext4 dev: "{{ node_baseline_longhorn_device }}" opts: "-L {{ node_baseline_longhorn_fs_label }}" - name: Mount the Longhorn data disk ansible.posix.mount: path: "{{ node_baseline_longhorn_mount }}" src: "LABEL={{ node_baseline_longhorn_fs_label }}" fstype: ext4 opts: "defaults,nofail,x-systemd.device-timeout=5s" state: mounted - name: Disable swap now ansible.builtin.command: swapoff -a changed_when: false - name: Neutralize swap entries in fstab (kubelet requires swap off) ansible.builtin.replace: path: /etc/fstab regexp: '^([^#].*\sswap\s.*)$' replace: '# \1 # disabled for k3s/kubelet' # --- DNS resilience (ROOT-CAUSE FIX) ----------------------------------------- # The nodes shipped with a SINGLE upstream resolver — PiHole (node_baseline_dns_primary, # 192.168.1.18) — set by cloud-init/netplan. PiHole answers the local `jackson.lan` zone # instantly but its EXTERNAL/recursive resolution flaps (verified: 0/25 for public names # like registry-1.docker.io/api.cloudflare.com while `gitea.jackson.lan` resolved 5/5). # With one upstream that is a hard SPOF: BOTH containerd image pulls (`lookup # registry-1.docker.io: Try again`) AND CoreDNS (`forward . /etc/resolv.conf`, so pods' # external DNS — external-dns→Cloudflare, oauth, etc.) fail whenever PiHole's recursion is # down. cert-manager already had to bypass it with public resolvers (DECISIONS-LOG Fix 5). # # FIX (additive, no policy bypass): keep PiHole PRIMARY (preserves `jackson.lan` + network # ad-blocking) and ADD the pfSense gateway (node_baseline_dns_fallback, 192.168.1.1) as a # FALLBACK. pfSense resolves the public zones reliably (verified). The resolver (glibc for # containerd; CoreDNS `forward` health-checking for pods) fails over to the fallback when # PiHole times out. A higher-priority netplan drop-in carries the change; cloud-init's # 50-cloud-init.yaml is regenerated on rebuild, so tofu's `dns.servers` list is updated to # match for provision-time parity (both layers converge on the same two servers). # NOTE: the *underlying* PiHole external-recursion outage is a separate LAN-wide problem to # investigate; this only makes the k3s nodes resilient to it. - name: Deploy resilient DNS (PiHole primary + pfSense fallback) via netplan drop-in ansible.builtin.copy: dest: /etc/netplan/99-k3s-dns.yaml owner: root group: root mode: "0600" content: | # Managed by the Ansible node_baseline role — do not edit by hand. # Additive DNS redundancy for the k3s nodes (see roles/node_baseline/tasks/main.yml): # PiHole PRIMARY (local zone + ad-block), pfSense gateway FALLBACK (reliable external # recursion). Overrides the single nameserver from 50-cloud-init.yaml. network: version: 2 ethernets: {{ node_baseline_primary_iface }}: nameservers: addresses: [{{ node_baseline_dns_primary }}, {{ node_baseline_dns_fallback }}] search: [{{ node_baseline_dns_search }}] notify: Apply netplan tags: [dns]