--- # TAGS — every task here carries one of `packages`, `system` or `disks`. An untagged run # is unchanged (all three run, in this order). The split exists because the PACKAGE floor # and the DATA-DISK work have different readiness: packages are safe on any node from # first contact, while the disk tasks need block devices that a new environment may not # have decided yet (prod: infra/PROD-PROVISIONING.md §2, whose decision is itself blocked # on a SMART read that needs `smartmontools` — which ships in node_baseline_packages). # `--tags packages` breaks that circle: it installs the package floor and NOTHING else. # ansible-playbook playbooks/node-baseline.yml -e k3s_hosts=k3s_prod --tags packages # Skipping is the guard: with `--tags packages` no `disks`-tagged task is selected, so a # BLOCKED-pending-disk-decision device path is never even templated, let alone handed to # mkfs. A FULL run against such a node stops at the assert below instead. - 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 tags: [packages] - 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" tags: [system] - name: Load kernel modules now community.general.modprobe: name: "{{ item }}" state: present loop: "{{ node_baseline_kernel_modules }}" tags: [system] - 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 tags: [system] - name: Enable and start iscsid (Longhorn prerequisite) ansible.builtin.systemd: name: iscsid enabled: true state: started tags: [system] # 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 tags: [system] - 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' }}" tags: [system] # Fail LOUD and EARLY rather than at mkfs time when an environment has not yet decided # its data-disk layout. group_vars/k3s_prod/vars.yml deliberately carries # `/dev/BLOCKED-pending-disk-decision-see-PROD-PROVISIONING-md-2` for both devices (see # its comments): leaving them unset would fall back to dev's virtio /dev/vdb|/dev/vdc, # which do not exist on bare metal either — a confusing failure that looks like a wiring # bug. This assert names the real blocker instead. It is a no-op on dev (/dev/vdb, # /dev/vdc) and is `disks`-tagged, so `--tags packages` skips it along with the disk work. - name: Assert this environment's data-disk devices have actually been decided ansible.builtin.assert: that: - "'BLOCKED' not in node_baseline_longhorn_device" - "'BLOCKED' not in node_baseline_etcd_device" fail_msg: >- node_baseline_longhorn_device / node_baseline_etcd_device are still the BLOCKED placeholders for this host's environment (longhorn={{ node_baseline_longhorn_device }}, etcd={{ node_baseline_etcd_device }}). The disk layout is an OPEN operator decision — see infra/PROD-PROVISIONING.md §2 — and must NOT be guessed. To install ONLY the package floor (e.g. smartmontools, so the SMART read that decision waits on can be taken) re-run this play with `--tags packages`, which skips every disk task including this assert. quiet: true tags: [disks] - 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 }}" tags: [disks] - 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 tags: [disks] # Dedicated SSD etcd disk (docs/plans/ssd-etcd-isolation.md). Formats /dev/vdc and # mounts it at the k3s etcd data dir BEFORE k3s starts, so a rebuilt node comes up with # etcd already isolated on SSD. Data-safe: community.general.filesystem never reformats # a disk that already carries a filesystem, so re-running against an already-migrated # node leaves the etcd data intact. On EXISTING nodes the one-time data migration # (stop k3s -> rsync db -> mount) is done manually per the plan's §4.3 before this # role would mount over a populated directory. - name: Create ext4 filesystem on the dedicated etcd SSD disk (data-safe; skips if present) community.general.filesystem: fstype: ext4 dev: "{{ node_baseline_etcd_device }}" opts: "-L {{ node_baseline_etcd_fs_label }}" tags: [disks] - name: Mount the dedicated etcd SSD disk at the k3s etcd data dir ansible.posix.mount: path: "{{ node_baseline_etcd_mount }}" src: "LABEL={{ node_baseline_etcd_fs_label }}" fstype: ext4 # noatime: fewer metadata writes under etcd's fsync-heavy load. No `nofail`: a # missing etcd disk MUST fail loudly rather than let k3s start etcd on the root HDD. opts: "defaults,noatime" dump: "0" passno: "2" state: mounted tags: [disks] - name: Disable swap now ansible.builtin.command: swapoff -a changed_when: false tags: [system] - 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' tags: [system]