- name: Site-Datei laden ansible.builtin.set_fact: knaster_pve: "{{ lookup('ansible.builtin.file', pve_site_file) | from_yaml }}" run_once: true delegate_to: localhost - name: Bootloader erkennen (systemd-boot bei ZFS-Root, sonst GRUB) ansible.builtin.stat: path: /etc/kernel/cmdline register: kernel_cmdline - name: IOMMU aktivieren (systemd-boot) ansible.builtin.replace: path: /etc/kernel/cmdline regexp: '^((?!.*intel_iommu=on).*)$' replace: '\1 {{ pve_iommu_cmdline }}' when: kernel_cmdline.stat.exists notify: refresh boot - name: IOMMU aktivieren (GRUB) ansible.builtin.lineinfile: path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((?!.*intel_iommu=on).*)"$' line: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 {{ pve_iommu_cmdline }}"' backrefs: true when: not kernel_cmdline.stat.exists notify: refresh boot - name: VFIO-Module laden ansible.builtin.copy: dest: /etc/modules-load.d/vfio.conf content: "{{ pve_vfio_modules | join('\n') }}\n" mode: "0644" notify: update initramfs # IDs auf dem Knoten lesen, nicht aus dem Gedächtnis: Grafik- und Audio-Funktion jeder Karte - name: NVIDIA-Funktionen des Knotens lesen ansible.builtin.shell: | set -o pipefail lspci -n -d 10de: | awk '{print $3}' | sort -u | paste -sd, args: executable: /bin/bash register: nvidia_ids check_mode: false changed_when: false failed_when: nvidia_ids.rc != 0 or nvidia_ids.stdout == '' - name: Karten an vfio-pci binden, bevor ein Grafik- oder Audiotreiber zugreift ansible.builtin.copy: dest: /etc/modprobe.d/vfio.conf content: | options vfio-pci ids={{ nvidia_ids.stdout }} softdep nouveau pre: vfio-pci softdep nvidia pre: vfio-pci softdep nvidiafb pre: vfio-pci softdep snd_hda_intel pre: vfio-pci mode: "0644" notify: update initramfs - name: NVIDIA-Treiber auf dem Host sperren (GPU gehört der VM) ansible.builtin.copy: dest: /etc/modprobe.d/blacklist-nvidia.conf content: | blacklist nouveau blacklist nvidia blacklist nvidiafb mode: "0644" notify: update initramfs - name: Speicher local darf Snippets enthalten (clusterweite Einstellung, einmal) ansible.builtin.shell: | set -euo pipefail c=$(pvesh get /storage/local --output-format json | python3 -c 'import json,sys; print(json.load(sys.stdin)["content"])') case ",$c," in *,snippets,*) echo unverändert ;; *) pvesm set local --content "$c,snippets"; echo geändert ;; esac args: executable: /bin/bash register: snippets changed_when: "'geändert' in snippets.stdout" run_once: true - name: Snippet-Verzeichnis ansible.builtin.file: path: "{{ pve_vendor_snippet | dirname }}" state: directory mode: "0755" - name: cloud-init-Vendor-Daten für GPU-VMs (qemu-guest-agent meldet tofu die DHCP-Adresse) ansible.builtin.copy: dest: "{{ pve_vendor_snippet }}" content: | #cloud-config packages: [qemu-guest-agent] runcmd: - [systemctl, enable, --now, qemu-guest-agent] mode: "0644" - name: Container auf diesem Knoten lesen ansible.builtin.command: ls /etc/pve/lxc/ register: local_cts check_mode: false changed_when: false - name: VMs auf diesem Knoten lesen ansible.builtin.command: ls /etc/pve/qemu-server/ register: local_vms check_mode: false changed_when: false # Infra-Gäste = Container (ct:) plus Dienst-VMs ohne GPU (vm:, D6); beide liegen auf tank mit HA - name: Infra-Gäste zusammenstellen ansible.builtin.set_fact: pve_infra_guests: >- {{ (knaster_pve.containers | dict2items | map('combine', {'kind': 'ct'}) | list) + (knaster_pve.service_vms | default({}) | dict2items | map('combine', {'kind': 'vm'}) | list) }} pve_local_guest_confs: "{{ local_cts.stdout_lines + local_vms.stdout_lines }}" - name: Bestehende Replikationsjobs lesen ansible.builtin.command: pvesr list register: pvesr_jobs check_mode: false changed_when: false - name: ZFS-Replikation für Infra-Gäste (erst wenn der Container auf diesem Knoten existiert) ansible.builtin.command: > pvesr create-local-job {{ item.0.value.vmid }}-{{ idx }} {{ item.1 }} --schedule '{{ knaster_pve.replication_schedule }}' --comment 'knaster {{ item.0.key }}' loop: "{{ pve_infra_guests | subelements('value.replicate_to', skip_missing=True) }}" vars: idx: "{{ item.0.value.replicate_to.index(item.1) }}" when: - item.0.value.node == inventory_hostname - (item.0.value.vmid | string) + '.conf' in pve_local_guest_confs - (item.0.value.vmid | string) + '-' + (idx | string) not in pvesr_jobs.stdout changed_when: true - name: HA-Konfiguration lesen ansible.builtin.command: ha-manager config register: ha_config check_mode: false changed_when: false - name: Infra-Gäste unter HA stellen (erst wenn der Container auf diesem Knoten existiert) ansible.builtin.command: > ha-manager add {{ item.kind }}:{{ item.value.vmid }} --state started --max_restart 1 --max_relocate 1 --comment 'knaster {{ item.key }}' loop: "{{ pve_infra_guests }}" when: - item.value.ha | default(false) - item.value.node == inventory_hostname - (item.value.vmid | string) + '.conf' in pve_local_guest_confs - (item.kind + ':' + (item.value.vmid | string)) not in ha_config.stdout changed_when: true - name: HA-Regeln lesen ansible.builtin.command: ha-manager rules config register: ha_rules check_mode: false changed_when: false # Strikt: Die Gäste liegen auf tank, den gibt es nur auf dem Heimatknoten und den Replikationszielen - name: Node-Affinity für Infra-Gäste (Heimatknoten bevorzugt, sonst Replikationsziele) ansible.builtin.command: > ha-manager rules add node-affinity knaster-{{ item.key }} --resources {{ item.kind }}:{{ item.value.vmid }} --nodes {{ ([item.value.node ~ ':2'] + (item.value.replicate_to | map('regex_replace', '$', ':1') | list)) | join(',') }} --strict 1 --comment 'knaster {{ item.key }}' loop: "{{ pve_infra_guests }}" when: - item.value.ha | default(false) - item.value.node == inventory_hostname - (item.value.vmid | string) + '.conf' in pve_local_guest_confs - ('knaster-' + item.key) not in ha_rules.stdout changed_when: true