声明式管理 Proxmox API 对象与 Samba DNS 上游
This commit is contained in:
@@ -1,4 +1,13 @@
|
|||||||
---
|
---
|
||||||
|
# Shared authentication for typed community.proxmox API modules. Secrets are
|
||||||
|
# runtime inputs; never put them in inventory or vars files.
|
||||||
|
pve_api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
|
||||||
|
pve_api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
|
||||||
|
pve_api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
|
||||||
|
pve_api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
|
||||||
|
pve_api_validate_certs: true
|
||||||
|
pve_api_python_interpreter: "{{ lookup('env', 'PVE_API_PYTHON') | default(ansible_playbook_python, true) }}"
|
||||||
|
|
||||||
ansible_user: root
|
ansible_user: root
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
|
||||||
|
|||||||
@@ -19,20 +19,13 @@ all:
|
|||||||
|
|
||||||
pve2:
|
pve2:
|
||||||
ansible_host: 192.168.10.7
|
ansible_host: 192.168.10.7
|
||||||
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G. THIS is the node that
|
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G.
|
||||||
# randomly froze. Kernel workaround below; also set BIOS
|
|
||||||
# "Power Supply Idle Control" -> "Typical Current Idle" (the more
|
|
||||||
# reliable half of the fix, and it can only be done physically).
|
|
||||||
pve_kernel_cmdline_extra: "idle=nomwait processor.max_cstate=1"
|
|
||||||
pve_install_disk: nvme0n1
|
pve_install_disk: nvme0n1
|
||||||
pve_bulk_disk: sda
|
pve_bulk_disk: sda
|
||||||
|
|
||||||
pve3:
|
pve3:
|
||||||
ansible_host: 192.168.10.9
|
ansible_host: 192.168.10.9
|
||||||
# Same Raven Ridge silicon as pve2 but has never frozen. Deliberately
|
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G.
|
||||||
# left WITHOUT the C-state workaround: processor.max_cstate=1 blocks
|
|
||||||
# deeper idle states and costs power, so it is not applied pre-emptively.
|
|
||||||
# If pve3 ever freezes, set pve_kernel_cmdline_extra here too.
|
|
||||||
pve_install_disk: nvme0n1
|
pve_install_disk: nvme0n1
|
||||||
pve_bulk_disk: sda
|
pve_bulk_disk: sda
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
proxmoxer==2.3.0
|
||||||
|
requests==2.34.2
|
||||||
@@ -13,3 +13,5 @@ collections:
|
|||||||
- name: ansible.posix # mount, authorized_key
|
- name: ansible.posix # mount, authorized_key
|
||||||
- name: community.general # proxmox_kvm (guest lifecycle). NOTE: deb822_repository
|
- name: community.general # proxmox_kvm (guest lifecycle). NOTE: deb822_repository
|
||||||
# is ansible.builtin, NOT community.general — see CLAUDE.md
|
# is ansible.builtin, NOT community.general — see CLAUDE.md
|
||||||
|
- name: community.proxmox
|
||||||
|
version: 2.0.0 # typed PVE API modules for HA, SDN and guest lifecycle
|
||||||
|
|||||||
@@ -14,12 +14,26 @@ pve_ha_resources:
|
|||||||
max_restart: 3
|
max_restart: 3
|
||||||
max_relocate: 2
|
max_relocate: 2
|
||||||
|
|
||||||
|
# pve3 is the normal home for the critical router. Non-strict preserves pve1
|
||||||
|
# and pve2 as failover targets when pve3 is unavailable.
|
||||||
|
pve_ha_rules:
|
||||||
|
- name: prefer-vyos-pve3
|
||||||
|
type: node-affinity
|
||||||
|
resources: ["vm:100"]
|
||||||
|
nodes: ["pve3:100", "pve1:10", "pve2:10"]
|
||||||
|
strict: false
|
||||||
|
comment: "Prefer critical VyOS router on pve3, away from CI nodes"
|
||||||
|
|
||||||
|
pve_ha_portable_vms:
|
||||||
|
- vmid: 100
|
||||||
|
node: pve3
|
||||||
|
cpu: x86-64-v2-AES
|
||||||
|
|
||||||
# --- Watchdog ---------------------------------------------------------------
|
# --- Watchdog ---------------------------------------------------------------
|
||||||
# ⚠ WHY THIS MATTERS: adding ANY HA resource arms fencing cluster-wide. PVE's
|
# ⚠ WHY THIS MATTERS: adding ANY HA resource arms fencing cluster-wide. PVE's
|
||||||
# default is `softdog`, a SOFTWARE watchdog — a kernel timer, which CANNOT fire if
|
# default is `softdog`, a SOFTWARE watchdog — a kernel timer, which CANNOT fire if
|
||||||
# the kernel itself is frozen. That is precisely the failure this cluster has
|
# the kernel itself is frozen. A hardware watchdog is independent silicon and
|
||||||
# actually seen (pve2's Raven Ridge idle freeze). A hardware watchdog is
|
# fires regardless.
|
||||||
# independent silicon and fires regardless.
|
|
||||||
#
|
#
|
||||||
# Verified available 2026-07-26:
|
# Verified available 2026-07-26:
|
||||||
# pve1 (Intel i3-6100U) -> iTCO_wdt (timeout 30s)
|
# pve1 (Intel i3-6100U) -> iTCO_wdt (timeout 30s)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Deliberately does NOT reboot. Swapping the watchdog under an armed cluster is a
|
# Deliberately does NOT reboot. Swapping the watchdog under an armed cluster is a
|
||||||
# manual, ordered operation (see ../../../README-ha.md); a surprise rolling reboot
|
# manual, ordered operation (see ../../../README-ha.md); a surprise rolling reboot
|
||||||
# of all three nodes is exactly what you do not want an idempotent play to do.
|
# of all three nodes is exactly what you do not want an idempotent play to do.
|
||||||
- name: reboot required for watchdog
|
- name: Reboot required for watchdog
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: >-
|
msg: >-
|
||||||
Watchdog config changed on {{ inventory_hostname }}. It takes effect on the
|
Watchdog config changed on {{ inventory_hostname }}. It takes effect on the
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
---
|
---
|
||||||
# Proxmox HA. Read roles/pve_ha/defaults/main.yml first — it explains why the
|
- name: Validate Proxmox API inputs
|
||||||
# resource list is short and why the watchdog choice is not cosmetic.
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pve_api_host | length > 0
|
||||||
|
- pve_api_user | length > 0
|
||||||
|
- pve_api_token_id | length > 0
|
||||||
|
- pve_api_token_secret | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Export PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and
|
||||||
|
PROXMOX_TOKEN_SECRET before running ha.yml.
|
||||||
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
tags: [ha]
|
||||||
|
|
||||||
# --- Watchdog ---------------------------------------------------------------
|
|
||||||
# Done BEFORE registering resources: adding a resource arms fencing, and arming
|
|
||||||
# fencing on a software watchdog is the weakest configuration.
|
|
||||||
- name: Select the hardware watchdog module
|
- name: Select the hardware watchdog module
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /etc/default/pve-ha-manager
|
path: /etc/default/pve-ha-manager
|
||||||
@@ -12,13 +21,9 @@
|
|||||||
line: "WATCHDOG_MODULE={{ pve_ha_watchdog_module }}"
|
line: "WATCHDOG_MODULE={{ pve_ha_watchdog_module }}"
|
||||||
create: false
|
create: false
|
||||||
when: pve_ha_watchdog_module | length > 0
|
when: pve_ha_watchdog_module | length > 0
|
||||||
notify: reboot required for watchdog
|
notify: Reboot required for watchdog
|
||||||
tags: [ha, watchdog]
|
tags: [ha, watchdog]
|
||||||
|
|
||||||
# watchdog-mux opens /dev/watchdog, which belongs to whichever watchdog registered
|
|
||||||
# FIRST. PVE loads softdog at boot, so it wins and the hardware module ends up as
|
|
||||||
# an unused watchdog1. Blacklisting softdog is what actually makes the hardware
|
|
||||||
# one take effect — setting WATCHDOG_MODULE alone does nothing.
|
|
||||||
- name: Blacklist softdog so the hardware watchdog claims /dev/watchdog
|
- name: Blacklist softdog so the hardware watchdog claims /dev/watchdog
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/modprobe.d/pve-ha-watchdog.conf
|
dest: /etc/modprobe.d/pve-ha-watchdog.conf
|
||||||
@@ -29,48 +34,147 @@
|
|||||||
# {{ pve_ha_watchdog_module }} register as watchdog0 and own /dev/watchdog.
|
# {{ pve_ha_watchdog_module }} register as watchdog0 and own /dev/watchdog.
|
||||||
blacklist softdog
|
blacklist softdog
|
||||||
when: pve_ha_watchdog_module | length > 0
|
when: pve_ha_watchdog_module | length > 0
|
||||||
notify: reboot required for watchdog
|
notify: Reboot required for watchdog
|
||||||
tags: [ha, watchdog]
|
tags: [ha, watchdog]
|
||||||
|
|
||||||
# ⚠ NOT DONE LIVE ON PURPOSE. Swapping the watchdog on a running node means
|
- name: Read current HA VM configurations
|
||||||
# stopping watchdog-mux and unloading softdog while fencing is armed — get the
|
ansible.builtin.uri:
|
||||||
# order wrong and the node self-fences (reboots). The safe live procedure is
|
url: >-
|
||||||
# documented in ../../README-ha.md; otherwise it simply takes effect on the next
|
https://{{ pve_api_host }}:8006/api2/json/nodes/{{ item.node }}/qemu/{{ item.vmid }}/config
|
||||||
# reboot, which is why the handler only WARNS rather than rebooting anything.
|
headers:
|
||||||
|
Authorization: >-
|
||||||
# --- Resources ---------------------------------------------------------------
|
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||||
- name: Read current HA resources
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
ansible.builtin.command: ha-manager status
|
return_content: true
|
||||||
register: pve_ha_status
|
loop: "{{ pve_ha_portable_vms }}"
|
||||||
changed_when: false
|
loop_control:
|
||||||
failed_when: false
|
label: "VM {{ item.vmid }}"
|
||||||
|
register: pve_ha_vm_config
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
tags: [ha]
|
tags: [ha]
|
||||||
|
|
||||||
- name: Register HA resources
|
- name: Reconcile portable CPU models for HA VMs
|
||||||
ansible.builtin.command: >-
|
community.proxmox.proxmox_kvm:
|
||||||
ha-manager add {{ item.sid }}
|
api_host: "{{ pve_api_host }}"
|
||||||
--state {{ item.state }}
|
api_user: "{{ pve_api_user }}"
|
||||||
--max_restart {{ item.max_restart }}
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
--max_relocate {{ item.max_relocate }}
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
node: "{{ item.node }}"
|
||||||
|
vmid: "{{ item.vmid }}"
|
||||||
|
cpu: "{{ item.cpu }}"
|
||||||
|
update: true
|
||||||
|
state: present
|
||||||
|
loop: "{{ pve_ha_portable_vms }}"
|
||||||
|
loop_control:
|
||||||
|
label: "VM {{ item.vmid }} -> {{ item.cpu }}"
|
||||||
|
index_var: vm_index
|
||||||
|
when: pve_ha_vm_config.results[vm_index].json.data.cpu != item.cpu
|
||||||
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
|
tags: [ha]
|
||||||
|
|
||||||
|
- name: Read current HA resources
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://{{ pve_api_host }}:8006/api2/json/cluster/ha/resources"
|
||||||
|
headers:
|
||||||
|
Authorization: >-
|
||||||
|
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
return_content: true
|
||||||
|
register: pve_ha_resource_state
|
||||||
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
tags: [ha]
|
||||||
|
|
||||||
|
- name: Reconcile HA resources through the PVE API
|
||||||
|
community.proxmox.proxmox_cluster_ha_resources:
|
||||||
|
api_host: "{{ pve_api_host }}"
|
||||||
|
api_user: "{{ pve_api_user }}"
|
||||||
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
name: "{{ item.sid }}"
|
||||||
|
state: present
|
||||||
|
hastate: "{{ item.state }}"
|
||||||
|
max_restart: "{{ item.max_restart }}"
|
||||||
|
max_relocate: "{{ item.max_relocate }}"
|
||||||
loop: "{{ pve_ha_resources }}"
|
loop: "{{ pve_ha_resources }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.sid }}"
|
label: "{{ item.sid }}"
|
||||||
# `ha-manager add` errors if the resource already exists, so gate on the status
|
when: >-
|
||||||
# output. This is what keeps a second run at changed=0.
|
_current_resource | length == 0 or
|
||||||
when: "'service ' ~ item.sid not in (pve_ha_status.stdout | default(''))"
|
_current_resource.state != item.state or
|
||||||
|
_current_resource.max_restart | int != item.max_restart | int or
|
||||||
|
_current_resource.max_relocate | int != item.max_relocate | int
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
|
_current_resource: >-
|
||||||
|
{{ pve_ha_resource_state.json.data |
|
||||||
|
selectattr('sid', 'equalto', item.sid) | first | default({}) }}
|
||||||
tags: [ha]
|
tags: [ha]
|
||||||
|
|
||||||
- name: Verify HA is armed and the resource is known
|
- name: Reconcile HA placement rules through the PVE API
|
||||||
ansible.builtin.command: ha-manager status
|
community.proxmox.proxmox_cluster_ha_rules:
|
||||||
register: pve_ha_verify
|
api_host: "{{ pve_api_host }}"
|
||||||
changed_when: false
|
api_user: "{{ pve_api_user }}"
|
||||||
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
state: present
|
||||||
|
type: "{{ item.type }}"
|
||||||
|
resources: "{{ item.resources }}"
|
||||||
|
nodes: "{{ item.nodes }}"
|
||||||
|
strict: "{{ item.strict }}"
|
||||||
|
comment: "{{ item.comment }}"
|
||||||
|
disable: false
|
||||||
|
loop: "{{ pve_ha_rules }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
tags: [ha]
|
tags: [ha]
|
||||||
|
|
||||||
- name: Show HA state
|
- name: Read HA placement rules through the PVE API
|
||||||
ansible.builtin.debug:
|
community.proxmox.proxmox_cluster_ha_rules_info:
|
||||||
msg: "{{ pve_ha_verify.stdout_lines }}"
|
api_host: "{{ pve_api_host }}"
|
||||||
|
api_user: "{{ pve_api_user }}"
|
||||||
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
register: pve_ha_rule_state
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
|
tags: [ha]
|
||||||
|
|
||||||
|
- name: Assert declared placement rules exist
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- >-
|
||||||
|
(pve_ha_rules | map(attribute='name') | list |
|
||||||
|
difference(pve_ha_rule_state.rules | default([]) |
|
||||||
|
map(attribute='rule') | list) | length) == 0
|
||||||
|
fail_msg: A declared HA placement rule is absent after reconciliation
|
||||||
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
tags: [ha]
|
tags: [ha]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
# Per-host kernel cmdline additions, driven by `pve_kernel_cmdline_extra` in the
|
# Per-host kernel cmdline additions, driven by `pve_kernel_cmdline_extra` in the
|
||||||
# inventory. Currently only pve2 sets it (Raven Ridge random-freeze workaround);
|
# inventory. No node currently sets it; keep the role for future kernel parameters.
|
||||||
# pve3 is identical silicon but is deliberately left alone -- see inventory.
|
|
||||||
#
|
#
|
||||||
# This is a no-op on hosts that do not define the variable.
|
# This is a no-op on hosts that do not define the variable.
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,81 @@
|
|||||||
---
|
---
|
||||||
# SDN config is cluster-wide (/etc/pve/sdn/), so everything runs once.
|
- name: Validate Proxmox API inputs
|
||||||
|
ansible.builtin.assert:
|
||||||
- name: Read existing zones
|
that:
|
||||||
ansible.builtin.command:
|
- pve_api_host | length > 0
|
||||||
cmd: "pvesh get /cluster/sdn/zones --output-format json"
|
- pve_api_user | length > 0
|
||||||
register: _zones
|
- pve_api_token_id | length > 0
|
||||||
changed_when: false
|
- pve_api_token_secret | length > 0
|
||||||
check_mode: false
|
fail_msg: Missing Proxmox API environment variables
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: Create the VLAN zone
|
- name: Read current PVE SDN zones
|
||||||
ansible.builtin.command:
|
ansible.builtin.uri:
|
||||||
cmd: >-
|
url: "https://{{ pve_api_host }}:8006/api2/json/cluster/sdn/zones"
|
||||||
pvesh create /cluster/sdn/zones --zone {{ pve_sdn_zone }} --type vlan
|
headers:
|
||||||
--bridge {{ pve_sdn_bridge }} --mtu {{ pve_sdn_mtu }}
|
Authorization: >-
|
||||||
--nodes {{ groups['pve'] | join(',') }}
|
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||||
when: pve_sdn_zone not in (_zones.stdout | from_json | map(attribute='zone') | list)
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
return_content: true
|
||||||
|
register: pve_sdn_zone_state
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: Read existing vnets
|
- name: Reconcile the PVE VLAN zone
|
||||||
ansible.builtin.command:
|
community.proxmox.proxmox_zone:
|
||||||
cmd: "pvesh get /cluster/sdn/vnets --output-format json"
|
api_host: "{{ pve_api_host }}"
|
||||||
register: _vnets
|
api_user: "{{ pve_api_user }}"
|
||||||
changed_when: false
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
check_mode: false
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
zone: "{{ pve_sdn_zone }}"
|
||||||
|
type: vlan
|
||||||
|
bridge: "{{ pve_sdn_bridge }}"
|
||||||
|
mtu: "{{ pve_sdn_mtu }}"
|
||||||
|
nodes: "{{ groups['pve'] | join(',') }}"
|
||||||
|
update: true
|
||||||
|
state: present
|
||||||
|
when: >-
|
||||||
|
_current_zone | length == 0 or
|
||||||
|
_current_zone.type != 'vlan' or
|
||||||
|
_current_zone.bridge != pve_sdn_bridge or
|
||||||
|
_current_zone.mtu | int != pve_sdn_mtu | int or
|
||||||
|
_current_zone.nodes != (groups['pve'] | join(','))
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
|
_current_zone: >-
|
||||||
|
{{ pve_sdn_zone_state.json.data |
|
||||||
|
selectattr('zone', 'equalto', pve_sdn_zone) | first | default({}) }}
|
||||||
|
|
||||||
- name: Create the VNets
|
# The module owns the SDN global lock and applies pending configuration before
|
||||||
ansible.builtin.command:
|
# releasing it; a separate unconditional `pvesh set /cluster/sdn` is harmful
|
||||||
cmd: >-
|
# because it reports changed on every run.
|
||||||
pvesh create /cluster/sdn/vnets --vnet {{ item.name }}
|
- name: Reconcile PVE VNets
|
||||||
--zone {{ pve_sdn_zone }} --tag {{ item.tag }}
|
community.proxmox.proxmox_vnet:
|
||||||
--alias '{{ item.alias }}'
|
api_host: "{{ pve_api_host }}"
|
||||||
|
api_user: "{{ pve_api_user }}"
|
||||||
|
api_token_id: "{{ pve_api_token_id }}"
|
||||||
|
api_token_secret: "{{ pve_api_token_secret }}"
|
||||||
|
validate_certs: "{{ pve_api_validate_certs }}"
|
||||||
|
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
vnet: "{{ item.name }}"
|
||||||
|
zone: "{{ pve_sdn_zone }}"
|
||||||
|
tag: "{{ item.tag }}"
|
||||||
|
alias: "{{ item.alias }}"
|
||||||
|
update: true
|
||||||
|
state: present
|
||||||
loop: "{{ pve_sdn_vnets }}"
|
loop: "{{ pve_sdn_vnets }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.name }} (vlan {{ item.tag }})"
|
label: "{{ item.name }} (VLAN {{ item.tag }})"
|
||||||
when: item.name not in (_vnets.stdout | from_json | map(attribute='vnet') | list)
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Apply the SDN configuration
|
|
||||||
# SDN changes stay PENDING until applied; without this the VNet bridges are
|
|
||||||
# never actually created on the nodes.
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "pvesh set /cluster/sdn"
|
|
||||||
register: _apply
|
|
||||||
changed_when: true
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Report
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: "pvesh get /cluster/sdn/vnets --output-format json | python3 -c \"import json,sys;[print(' ',v['vnet'],'vlan',v.get('tag'),'zone',v.get('zone')) for v in json.load(sys.stdin)]\""
|
|
||||||
register: _rep
|
|
||||||
changed_when: false
|
|
||||||
run_once: true
|
|
||||||
|
|
||||||
- name: Show it
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "{{ _rep.stdout_lines }}"
|
|
||||||
run_once: true
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
no_log: true
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ pve_vms:
|
|||||||
description: "VyOS router: OSPF peer to the NEC IX, gateway for the SDN VNets."
|
description: "VyOS router: OSPF peer to the NEC IX, gateway for the SDN VNets."
|
||||||
cores: 2
|
cores: 2
|
||||||
memory: 2048
|
memory: 2048
|
||||||
|
# Portable across Intel pve1 and AMD pve2/pve3. `host` transferred memory
|
||||||
|
# but failed target-side resume during the 2026-09-17 migration.
|
||||||
|
cpu: x86-64-v2-AES
|
||||||
disk: "pve-rg:8"
|
disk: "pve-rg:8"
|
||||||
# Headless by design: UEFI + serial console, NO emulated VGA. VyOS's own
|
# Headless by design: UEFI + serial console, NO emulated VGA. VyOS's own
|
||||||
# kernel cmdline carries console=ttyS0, and the installer was answered with
|
# kernel cmdline carries console=ttyS0, and the installer was answered with
|
||||||
@@ -56,6 +59,7 @@ pve_vms:
|
|||||||
# are AMD.
|
# are AMD.
|
||||||
cores: 4
|
cores: 4
|
||||||
memory: 4096
|
memory: 4096
|
||||||
|
cpu: host
|
||||||
disk: "pve-rg:40"
|
disk: "pve-rg:40"
|
||||||
bios: ovmf
|
bios: ovmf
|
||||||
machine: q35
|
machine: q35
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
--description '{{ item.item.description }}'
|
--description '{{ item.item.description }}'
|
||||||
--machine {{ item.item.machine | default('q35') }}
|
--machine {{ item.item.machine | default('q35') }}
|
||||||
--bios {{ item.item.bios | default('ovmf') }}
|
--bios {{ item.item.bios | default('ovmf') }}
|
||||||
--memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu host
|
--memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu {{ item.item.cpu | default('x86-64-v2-AES') }}
|
||||||
--scsihw virtio-scsi-single
|
--scsihw virtio-scsi-single
|
||||||
--scsi0 {{ item.item.disk }},discard=on,ssd=1
|
--scsi0 {{ item.item.disk }},discard=on,ssd=1
|
||||||
--efidisk0 {{ item.item.disk.split(':')[0] }}:1,efitype=4m,pre-enrolled-keys=0
|
--efidisk0 {{ item.item.disk.split(':')[0] }}:1,efitype=4m,pre-enrolled-keys=0
|
||||||
|
|||||||
@@ -71,3 +71,9 @@ vyos_ospf_router_id: "192.168.10.2"
|
|||||||
vyos_ospf_area: "0"
|
vyos_ospf_area: "0"
|
||||||
vyos_ospf_networks:
|
vyos_ospf_networks:
|
||||||
- "192.168.10.0/24"
|
- "192.168.10.0/24"
|
||||||
|
|
||||||
|
# Stable K3s datastore endpoint. Only the declared writable primary is present
|
||||||
|
# in the backend; PostgreSQL health checks must never be treated as promotion.
|
||||||
|
vyos_postgresql_listen_address: "10.60.0.1"
|
||||||
|
vyos_postgresql_port: 5432
|
||||||
|
vyos_postgresql_primary_address: "10.60.0.11"
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ set protocols ospf area {{ vyos_ospf_area }} network {{ i.network }}
|
|||||||
set protocols ospf interface {{ i.iface }} passive
|
set protocols ospf interface {{ i.iface }} passive
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{# --- K3s PostgreSQL datastore: fixed endpoint, explicitly selected primary --- #}
|
||||||
|
set load-balancing haproxy service sandbox-postgresql listen-address {{ vyos_postgresql_listen_address }}
|
||||||
|
set load-balancing haproxy service sandbox-postgresql port {{ vyos_postgresql_port }}
|
||||||
|
set load-balancing haproxy service sandbox-postgresql mode 'tcp'
|
||||||
|
set load-balancing haproxy service sandbox-postgresql backend 'sandbox-postgresql-primary'
|
||||||
|
set load-balancing haproxy backend sandbox-postgresql-primary mode 'tcp'
|
||||||
|
set load-balancing haproxy backend sandbox-postgresql-primary health-check 'pgsql'
|
||||||
|
set load-balancing haproxy backend sandbox-postgresql-primary server primary address {{ vyos_postgresql_primary_address }}
|
||||||
|
set load-balancing haproxy backend sandbox-postgresql-primary server primary port {{ vyos_postgresql_port }}
|
||||||
|
set load-balancing haproxy backend sandbox-postgresql-primary server primary check
|
||||||
|
|
||||||
{# --- management --- #}
|
{# --- management --- #}
|
||||||
set service ssh port 22
|
set service ssh port 22
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
- name: 更新现有 AD 域控 DNS 上游
|
||||||
|
hosts: samba_dc
|
||||||
|
become: true
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: 仅应用 DNS 转发器配置
|
||||||
|
ansible.builtin.import_role:
|
||||||
|
name: samba_ad_dc
|
||||||
|
tasks_from: dns_forwarder
|
||||||
@@ -7,7 +7,7 @@ samba_ad_realm: "AD.DDUPAN.TOP"
|
|||||||
samba_ad_domain: "DDUPAN"
|
samba_ad_domain: "DDUPAN"
|
||||||
samba_ad_dc_hostname: "dc1"
|
samba_ad_dc_hostname: "dc1"
|
||||||
samba_ad_dc_ip: "192.168.10.5"
|
samba_ad_dc_ip: "192.168.10.5"
|
||||||
samba_ad_dns_forwarder: "192.168.10.1"
|
samba_ad_dns_forwarder: "192.168.10.127" # Blocky:DN42 条件转发及公网解析
|
||||||
samba_ad_reverse_zone: "10.168.192.in-addr.arpa" # reverse of 192.168.10.0/24
|
samba_ad_reverse_zone: "10.168.192.in-addr.arpa" # reverse of 192.168.10.0/24
|
||||||
|
|
||||||
# Static records now live in ../../dns/records.yml and are reconciled as complete
|
# Static records now live in ../../dns/records.yml and are reconciled as complete
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
- name: 更新 Samba DNS 转发器并校验配置
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/samba/smb.conf
|
||||||
|
regexp: '^\s*dns forwarder\s*='
|
||||||
|
insertafter: '^\[global\]'
|
||||||
|
line: "\tdns forwarder = {{ samba_ad_dns_forwarder }}"
|
||||||
|
validate: '/usr/bin/testparm -s %s'
|
||||||
|
backup: true
|
||||||
|
notify: restart samba-ad-dc
|
||||||
@@ -87,6 +87,9 @@
|
|||||||
creates: /var/lib/samba/private/sam.ldb
|
creates: /var/lib/samba/private/sam.ldb
|
||||||
no_log: true # keep the admin password out of logs
|
no_log: true # keep the admin password out of logs
|
||||||
|
|
||||||
|
- name: 持续管理已初始化域控的 DNS 上游
|
||||||
|
ansible.builtin.import_tasks: dns_forwarder.yml
|
||||||
|
|
||||||
- name: Install the generated krb5.conf system-wide
|
- name: Install the generated krb5.conf system-wide
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: /var/lib/samba/private/krb5.conf
|
src: /var/lib/samba/private/krb5.conf
|
||||||
|
|||||||
Reference in New Issue
Block a user