321 lines
11 KiB
YAML
321 lines
11 KiB
YAML
---
|
|
# playbooks/preflight/gather_hardware_facts.yml
|
|
#
|
|
# Purpose:
|
|
# Gather comprehensive hardware specifications from Proxmox hosts for
|
|
# capacity planning and topology analysis (e.g., Docker Swarm 3-node cluster)
|
|
#
|
|
# Usage:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/preflight/gather_hardware_facts.yml \
|
|
# -e "target_hosts=proxmox_cluster" \
|
|
# -e "output_file=hardware_comparison_$(date +%Y%m%d).yml"
|
|
#
|
|
# Modules Explained:
|
|
# - ansible.builtin.gather_facts: Collects system facts (CPU, RAM, network, OS)
|
|
# WHY: Native, zero-install, idempotent. Beats shell commands because it's structured.
|
|
# - ansible.builtin.setup: Explicit fact-gathering (redundant but explicit docs)
|
|
# - ansible.builtin.command: For Proxmox-specific queries (pvesh, pveversion)
|
|
# WHY: Cleaner than shell for simple commands; no shell interpretation risk
|
|
# - ansible.builtin.copy: Save structured YAML report locally
|
|
# WHY: Idempotent, checksummed, handles permissions correctly
|
|
#
|
|
# Idempotency:
|
|
# All tasks are read-only (gather facts, query status). No state changes.
|
|
# Safe to run multiple times without side effects.
|
|
#
|
|
# Safety Notes:
|
|
# - Does NOT modify Proxmox configuration or cluster state
|
|
# - Requires root SSH access (standard Proxmox assumption)
|
|
# - Network scanning is passive (no stress-testing)
|
|
|
|
- name: "Gather Hardware Facts from Proxmox Hosts"
|
|
hosts: "{{ target_hosts | default('proxmox_cluster') }}"
|
|
gather_facts: true
|
|
become: true
|
|
vars:
|
|
output_dir: "{{ playbook_dir }}/../../outputs"
|
|
output_file: "{{ output_dir }}/hardware_facts_{{ ansible_date_time.iso8601_basic_short }}.yml"
|
|
|
|
pre_tasks:
|
|
- name: "Validate target hosts are Proxmox nodes"
|
|
ansible.builtin.assert:
|
|
that:
|
|
- inventory_hostname.startswith('pve')
|
|
fail_msg: "This playbook is for Proxmox nodes (pve*). Target: {{ inventory_hostname }}"
|
|
tags:
|
|
- validate
|
|
|
|
- name: "Ensure output directory exists"
|
|
ansible.builtin.file:
|
|
path: "{{ output_dir }}"
|
|
state: directory
|
|
mode: '0755'
|
|
delegate_to: localhost
|
|
run_once: true
|
|
tags:
|
|
- setup
|
|
|
|
tasks:
|
|
# -------------------------------------------------------------------------
|
|
# SECTION 1: SYSTEM & OS FACTS
|
|
# -------------------------------------------------------------------------
|
|
|
|
- name: "1.1 Gather all facts (native Ansible)"
|
|
ansible.builtin.setup:
|
|
tags:
|
|
- facts
|
|
- always
|
|
|
|
- name: "1.2 Extract CPU model and frequencies"
|
|
ansible.builtin.set_fact:
|
|
hw_cpu_model: "{{ ansible_processor | first | default('unknown') }}"
|
|
hw_cpu_cores: "{{ ansible_processor_vcpus | default('unknown') }}"
|
|
hw_cpu_cores_per_socket: "{{ ansible_processor_cores | default('unknown') }}"
|
|
hw_cpu_count: "{{ ansible_processor_count | default(1) }}"
|
|
tags:
|
|
- facts
|
|
- cpu
|
|
|
|
- name: "1.3 Query CPU frequency (max freq)"
|
|
ansible.builtin.shell: "grep 'cpu MHz' /proc/cpuinfo | head -1"
|
|
register: _cpu_freq_output
|
|
changed_when: false
|
|
failed_when: false
|
|
tags:
|
|
- facts
|
|
- cpu
|
|
|
|
- name: "1.4 Parse CPU frequency"
|
|
ansible.builtin.set_fact:
|
|
hw_cpu_freq_mhz: "{{ (_cpu_freq_output.stdout | regex_search(':\\s+([0-9.]+)') | regex_replace('[^0-9.]', '')).split('.')[0] | float if _cpu_freq_output.stdout else 'unknown' }}"
|
|
tags:
|
|
- facts
|
|
- cpu
|
|
|
|
- name: "1.5 Extract RAM information"
|
|
ansible.builtin.set_fact:
|
|
hw_ram_total_mb: "{{ ansible_memtotal_mb | default('unknown') }}"
|
|
hw_ram_free_mb: "{{ ansible_memfree_mb | default('unknown') }}"
|
|
tags:
|
|
- facts
|
|
- ram
|
|
|
|
- name: "1.6 Extract OS and kernel"
|
|
ansible.builtin.set_fact:
|
|
hw_os_family: "{{ ansible_os_family }}"
|
|
hw_os_distro: "{{ ansible_distribution }}"
|
|
hw_os_release: "{{ ansible_distribution_release }}"
|
|
hw_kernel: "{{ ansible_kernel }}"
|
|
hw_uptime_seconds: "{{ ansible_uptime_seconds }}"
|
|
tags:
|
|
- facts
|
|
- os
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SECTION 2: STORAGE FACTS
|
|
# -------------------------------------------------------------------------
|
|
|
|
- name: "2.1 Gather disk information"
|
|
ansible.builtin.set_fact:
|
|
hw_disks: "{{ ansible_devices | default({}) | dict2items | map(attribute='key') | list }}"
|
|
tags:
|
|
- facts
|
|
- storage
|
|
|
|
- name: "2.2 Gather mount points and usage"
|
|
ansible.builtin.shell: |
|
|
df -h | tail -n +2 | awk '{print $1 " (" $4 " available of " $2 ")"}'
|
|
register: _mount_output
|
|
changed_when: false
|
|
tags:
|
|
- facts
|
|
- storage
|
|
|
|
- name: "2.3 Parse mount data"
|
|
ansible.builtin.set_fact:
|
|
hw_mounts: "{{ _mount_output.stdout_lines | default([]) }}"
|
|
tags:
|
|
- facts
|
|
- storage
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SECTION 3: NETWORK FACTS
|
|
# -------------------------------------------------------------------------
|
|
|
|
- name: "3.1 Gather network interface details"
|
|
ansible.builtin.set_fact:
|
|
hw_network_interfaces: "{{ ansible_interfaces | default([]) }}"
|
|
tags:
|
|
- facts
|
|
- network
|
|
|
|
# -----------------------------------------------------------------------
|
|
# SECTION 4: PROXMOX-SPECIFIC FACTS
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: "4.1 Query Proxmox version"
|
|
ansible.builtin.command: "pveversion"
|
|
register: _pveversion_output
|
|
changed_when: false
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.2 Parse Proxmox version"
|
|
ansible.builtin.set_fact:
|
|
hw_proxmox_version: "{{ _pveversion_output.stdout | regex_search('proxmox-ve\\s+([0-9.]+)') | regex_replace('[^0-9.]', '') }}"
|
|
hw_pveversion_full: "{{ _pveversion_output.stdout }}"
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.3 Query Proxmox cluster status"
|
|
ansible.builtin.command: "pvesh get /cluster/resources --output-format json"
|
|
register: _cluster_resources
|
|
changed_when: false
|
|
failed_when: false
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.4 Count VMs and containers on this host"
|
|
ansible.builtin.set_fact:
|
|
hw_vm_count: "{{ (_cluster_resources.stdout | from_json | selectattr('node', 'equalto', inventory_hostname) | selectattr('type', 'match', 'qemu|lxc') | list | length) if _cluster_resources.rc == 0 else 'unknown' }}"
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.5 Query Proxmox cluster info"
|
|
ansible.builtin.command: "pvesh get /cluster/status --output-format json"
|
|
register: _cluster_status
|
|
changed_when: false
|
|
failed_when: false
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.6 Parse cluster membership"
|
|
ansible.builtin.set_fact:
|
|
hw_cluster_name: "{{ (_cluster_status.stdout | from_json | first).cluster | default('not-clustered') if _cluster_status.rc == 0 and (_cluster_status.stdout | from_json | length) > 0 else 'not-clustered' }}"
|
|
hw_cluster_nodes: "{{ (_cluster_status.stdout | from_json | map(attribute='name') | list) if _cluster_status.rc == 0 else [] }}"
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
- name: "4.7 Check if node is clustered"
|
|
ansible.builtin.set_fact:
|
|
hw_is_clustered: "{{ inventory_hostname in hw_cluster_nodes }}"
|
|
tags:
|
|
- facts
|
|
- proxmox
|
|
|
|
# -----------------------------------------------------------------------
|
|
# SECTION 5: SYSTEM LOAD & CAPACITY
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: "5.1 Gather system load"
|
|
ansible.builtin.set_fact:
|
|
hw_load_1min: "{{ ansible_load | default([0, 0, 0]) | first }}"
|
|
hw_load_5min: "{{ (ansible_load | default([0, 0, 0]))[1] }}"
|
|
hw_load_15min: "{{ (ansible_load | default([0, 0, 0]))[2] }}"
|
|
tags:
|
|
- facts
|
|
- load
|
|
|
|
- name: "5.2 Calculate CPU usage percentage"
|
|
ansible.builtin.set_fact:
|
|
hw_cpu_load_percent: "{{ ((hw_load_1min | float / hw_cpu_cores | int) * 100) | int if hw_load_1min != 0 else 0 }}"
|
|
tags:
|
|
- facts
|
|
- load
|
|
|
|
post_tasks:
|
|
- name: "Build hardware summary fact"
|
|
ansible.builtin.set_fact:
|
|
hardware_summary:
|
|
hostname: "{{ inventory_hostname }}"
|
|
ip_address: "{{ ansible_default_ipv4.address }}"
|
|
fqdn: "{{ ansible_fqdn }}"
|
|
timestamp: "{{ ansible_date_time.iso8601 }}"
|
|
|
|
system:
|
|
os: "{{ hw_os_distro }} {{ hw_os_release }}"
|
|
kernel: "{{ hw_kernel }}"
|
|
uptime_days: "{{ (hw_uptime_seconds / 86400) | int }}"
|
|
|
|
cpu:
|
|
model: "{{ hw_cpu_model }}"
|
|
sockets: "{{ hw_cpu_count }}"
|
|
cores_per_socket: "{{ hw_cpu_cores_per_socket }}"
|
|
total_cores: "{{ hw_cpu_cores }}"
|
|
max_frequency_mhz: "{{ hw_cpu_freq_mhz | int if hw_cpu_freq_mhz != 'unknown' else 'unknown' }}"
|
|
current_1min_load: "{{ hw_load_1min }}"
|
|
cpu_load_percent: "{{ hw_cpu_load_percent }}%"
|
|
|
|
memory:
|
|
total_mb: "{{ hw_ram_total_mb }}"
|
|
total_gb: "{{ (hw_ram_total_mb | int / 1024) | int if hw_ram_total_mb != 'unknown' else 'unknown' }}"
|
|
free_mb: "{{ hw_ram_free_mb }}"
|
|
free_gb: "{{ (hw_ram_free_mb | int / 1024) | int if hw_ram_free_mb != 'unknown' else 'unknown' }}"
|
|
|
|
storage:
|
|
disks_detected: "{{ hw_disks | length }}"
|
|
disk_list: "{{ hw_disks }}"
|
|
mounts_summary: "{{ hw_mounts }}"
|
|
|
|
network:
|
|
interfaces_count: "{{ hw_network_interfaces | length }}"
|
|
interface_list: "{{ hw_network_interfaces }}"
|
|
|
|
proxmox:
|
|
version: "{{ hw_proxmox_version }}"
|
|
version_full: "{{ hw_pveversion_full }}"
|
|
cluster_name: "{{ hw_cluster_name | default('not-clustered') }}"
|
|
is_clustered: "{{ hw_is_clustered }}"
|
|
cluster_members: "{{ hw_cluster_nodes | default([]) }}"
|
|
vms_and_containers: "{{ hw_vm_count }}"
|
|
tags:
|
|
- summary
|
|
- always
|
|
|
|
- name: "Display hardware summary"
|
|
ansible.builtin.debug:
|
|
msg: "{{ hardware_summary }}"
|
|
tags:
|
|
- summary
|
|
- always
|
|
|
|
- name: "Collect all hardware facts for report"
|
|
ansible.builtin.set_fact:
|
|
all_hardware_facts: "{{ all_hardware_facts | default({}) | combine({inventory_hostname: hardware_summary}) }}"
|
|
tags:
|
|
- report
|
|
|
|
- name: "Write hardware comparison report"
|
|
ansible.builtin.copy:
|
|
content: |
|
|
---
|
|
# Hardware Facts Report
|
|
# Generated: {{ ansible_date_time.iso8601 }}
|
|
# Hosts Analyzed: {{ groups[target_hosts | default('proxmox_cluster')] | length }}
|
|
#
|
|
# Usage:
|
|
# This report compares hardware specifications for Docker Swarm topology planning.
|
|
# See README in documentation/architecture/ for capacity analysis.
|
|
|
|
{{ all_hardware_facts | to_nice_yaml }}
|
|
dest: "{{ output_file }}"
|
|
mode: '0644'
|
|
delegate_to: localhost
|
|
run_once: true
|
|
tags:
|
|
- report
|
|
|
|
- name: "Report output file location"
|
|
ansible.builtin.debug:
|
|
msg: "✓ Hardware facts saved to: {{ output_file }}"
|
|
delegate_to: localhost
|
|
run_once: true
|
|
tags:
|
|
- report
|