rationale Link to heading

let’s be honest it’s a pain in the ass to maintain a static inventory in proxmox infrastructure.

let’s have a dynamic one

requirements : Link to heading
  • ansible controller with plugins and proxmoxer
  • proxmox 7 or 8 with proxmoxer and api user
  • a bunch of ct/vm with ip and correct ssh setup
setup proxmox user Link to heading
  • we use api instead of pam credentials

as we need this user to deploy ct/vm in the future we give him/her Administrator role

pveum group add admin -comment "System Administrators"
pveum acl modify / -group admin -role Administrator

we create a new admin user and attach him/her to the sysadmin group :

pveum user add 6admin@pve -comment "sysadmin"
pveum user modify 6admin@pve -group admin

now we need to create the token :

  • warning : we use privsep 0 because we’ll use this token to do some administrative stuff. read the documentation to use a privilege separation.
pveum user token add 6admin@pve Administrator -privsep 0
┌──────────────┬──────────────────────────────────────┐
│ key          │ value                                │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ 6admin@pve!Administrator             │
├──────────────┼──────────────────────────────────────┤
│ info         │ {"privsep":0}├──────────────┼──────────────────────────────────────┤
│ value        │ redacted                             │
└──────────────┴──────────────────────────────────────┘

note that token there is no way to find it afterwards

check users permissions :

pveum user token permissions 6admin@pve Administrator

check if your token works from a remote client :

  • never keep a cleartext token in your shell history use a space at the beginning of the command to exclude it from the history.
 curl -k -H 'Authorization: PVEAPIToken=6admin@pve!Administrator=redacted' https://xxx.xxx.xxx.xxx:8006/api2/json/

you should see this

{"data":[{"subdir":"version"},{"subdir":"cluster"},{"subdir":"nodes"},{"subdir":"storage"},{"subdir":"access"},{"subdir":"pools"}]}

if not make sure you followed the aforementioned steps and read tha mothafuckin doc

setup proxmoxer on proxmox Link to heading

simply install the packaged proxmoxer

sudo apt install -y python3-proxmoxer

if it’s buggy or doesnt fit your need you should get it with pip

sudo apt install python3-pip && pip install proxmoxer
setup your ansible controller Link to heading

we assume your ansible directory is ~/ansible

we need some ansible collection

ansible-galaxy collection install community.general

enable the correct options

ansible.cfg

[defaults]
inventory_plugins  = ~/ansible/plugins/inventory

[inventory]
enable_plugins = host_list, yaml, constructed, ini, auto, community.general.proxmox
use_extra_vars = true
generate on the fly vaulted secrets Link to heading

you can generate your vaulted passwords or anything on the fly as follows

  • remember never keep cleartext secrets in your history
 echo -n 'YOURSUPERTOKEN' | ansible-vault encrypt_string
  • the ansible vault password has to be the same as your real vault password if you have a big fat vault.

you’ll have this output :

!vault |
          $ANSIBLE_VAULT;1.1;AES256
          32623338643462356533313438646136346464346432353437623839663635323466323133333732
          3733363633373565363330633235643762326133383131330a376665313934626531613331373031
          38633339383363346536616464343066336464646636323766313533613731343162333762663464
          3031663039303363620a633964373866633435623330313130326666373739636166633162333461
          6139
setup the plugin Link to heading

create the plugin directory whithin you ansible directory and edit the file matching your setup

mkdir -p ~/ansible/plugins/inventory
  • the filename of your plugin must end as follows *.proxmox.yml

~/ansible/plugins/inventory/myserver.proxmox.yml

plugin: community.general.proxmox
url: https://xxx.xxx.xxx.xxx:8006
user: 6admin@pve
token_id: Administrator
# generated via echo -n 'YOURSUPERTOKEN' | ansible-vault encrypt_string
# the password has to be the same as your vault for more convenience
token_secret: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          32623338643462356533313438646136346464346432353437623839663635323466323133333732
          3733363633373565363330633235643762326133383131330a376665313934626531613331373031
          38633339383363346536616464343066336464646636323766313533613731343162333762663464
          3031663039303363620a633964373866633435623330313130326666373739636166633162333461
          6139

validate_certs: false
want_facts: true

# create groups based on proxmox tags
groups:
  preprod: "'preprod' in (proxmox_tags_parsed|list)"
  prod: "'prod' in (proxmox_tags_parsed|list)"
  public: "'public' in (proxmox_tags_parsed|list)"
  lab: "'lab' in (proxmox_tags_parsed|list)"
  decom: "'decom' in (proxmox_tags_parsed|list)"
  staff: "'staff' in (proxmox_tags_parsed|list)"

# create hostvars
compose:
  ansible_port: 50022
  ansible_user: "'ansible'"
  ansible_ssh_private_key_file: "'~/.ssh/ansible_srv'"
  ansible_become_method: "'sudo'"

# it seems the plugin cannot interpret external vars or vault so you can  use -K with cli
# or simply specify extra-vars with  -e "ansible_become_password="$(bw get password ansible_pass)" "
# this example get a password from a vaultwarden instance

# parse the IP address of host (split is used to strip '/CIDR' notation) thanks @asshall
  ansible_host: proxmox_agent_interfaces[1]["ip-addresses"][0] | default("") | split('/') | first

# filters are a way to target specific hosts
#filters:
# only add hosts that have an ip address via agent
#  - proxmox_agent_interfaces is defined

# for testing purposes
#strict: true
setup ct/vm Link to heading

setup user/ssh on your targeted vm

quick and dirty steps

useradd -m -s /bin/bash -m -G sudo ansible
passwd ansible

add your pubkey then modify your sshd_config to fit your settings and restart sshd service

systemctl restart sshd

naturally check if ssh connectivity is ok.

exploit tha dynamic thing Link to heading

finally you can list your inventory with

ansible-inventory -i plugins/inventory/myserver.proxmox.yml --graph
  |--@lab:
  |  |--netbox
  |  |--deblab4
  |  |--pxelab
  |  |--rhelab
  |--@prod:
  |  |--pgsql
  |  |--gitea
  |  |--download

now the real game begin.

to execute a playbook against all your proxmox’s ct/vm.

example playbook to get informations about vm/ct

gather_inventory.yml

---
- hosts: all
  tasks:

    - name: 'Gather vm/lxc informations'
      set_fact:
        hostname: "{{ ansible_hostname }}"
        os_version: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
        kernel: "{{ ansible_kernel }}"
        cpu: "{{ ansible_processor_count }} cores"
        ram: "{{ ansible_memtotal_mb }} MB"
        interface_name: "{{ ansible_default_ipv4.interface }}"
        mac: "{{ ansible_default_ipv4.macaddress }}"
        ip: "{{ ansible_default_ipv4.address }}"

    - name: 'Append informations to file on controller'
      lineinfile:
        dest: "{{ ansible_host }}.inventory.txt"
        line: " hostname : {{ hostname }} os: {{ os_version }} kernel : {{ kernel }} cpu : {{ cpu }} ram : {{ ram }} interface : {{ interface_name }} mac : {{ mac }} ip : {{ ip }} "
        create: true
        insertbefore: EOF
      delegate_to: localhost
...
ansible-playbook -i plugins/inventory/tifa.proxmox.yml playbooks/gather_inventory.yml -Kk

-K is for asking ansible_become_password

you can use extra vars to automate the process

in this example i fetch ansible_become_password from a vaultwarden instance i already setup/unlocked on my ansible controller

ansible-playbook -i plugins/inventory/tifa.proxmox.yml playbooks/deploy_stuff_with_sudo.yml -e "ansible_become_password="$(bw get password ansible_pass)" "

to limit :

  • specific group i use -l prod
  • specific group and exclude another -l 'proxmox_all_running,!lab'

so anynone with no ansible skills can tag a vm/ct via webui can automate some process when further ansible roles are deployed

some tag like upgrade destroy etc …

enjoy

troubleshooting : Link to heading

some vm/ct can have this error :

fatal: [deblab2]: UNREACHABLE! => {                                                                                                                                                           
    "changed": false,                                                                                                                                                                         
    "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname deblab2: Name or service not known",                                                                       
    "unreachable": true                                                                                                                                                                       
}    

could be due to this

sudo: unable to resolve host deblab4: Name or service not known

double check your /etc/hostname and /etc/hosts inside vm/ct

ressources : Link to heading

  • proxmox users api token :

https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens

https://pve.proxmox.com/pve-docs/chapter-pveum.html#pveum_tokens

https://pve.proxmox.com/wiki/User_Management#pveum_tokens

  • ansible stuff :

https://docs.ansible.com/ansible/latest/vault_guide/vault_encrypting_content.html

https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_user_info_module.html

https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_inventory.html

  • connect to vm/lxc via tty

https://stackoverflow.com/questions/56208668/set-ansible-connection-differently-according-to-given-condition

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ssh_connection.html#parameter-use_tty

  • proxmox.yml name problem

https://stackoverflow.com/questions/74431714/ansible-build-dynamic-inventory-with-proxmox

  • awx proxmox

https://github.com/JohnCardenas/ansible-awx-inv-proxmox