Proxmox Host management with Ansible

What is Ansible?

Ansible is a powerful automation tool widely used for managing systems and deploying applications. When combined with Proxmox, a popular open-source virtualization platform, Ansible becomes an even more powerful tool to manage virtual machines (VMs), containers, and the Proxmox host itself. In this blog post, we’ll dive into how to use Ansible to manage a Proxmox host, simplifying administration and providing efficient automation.

What is Proxmox?

Proxmox Virtual Environment (VE) is an open-source platform for managing virtual machines and containers. It supports KVM for virtual machines and LXC for containers, making it an excellent solution for managing virtualized environments. Proxmox offers a web interface, but many administrators prefer using automation tools like Ansible to streamline their workflow.

Why Use Ansible with Proxmox?

While Proxmox has a comprehensive web interface for manual management, automation is key when managing multiple nodes or maintaining consistency across large infrastructures. Ansible allows you to:

  • Automate VM creation and management: Spin up, modify, and delete VMs with ease.
  • Consistent configuration: Maintain configurations across hosts and containers in your Proxmox environment.
  • Centralized management: Easily manage multiple Proxmox hosts from a single location.
  • No agent required: Ansible operates over SSH, so no agent needs to be installed on the target systems.

Prerequisites

Before diving into automation, ensure you have the following prerequisites in place:

  1. Proxmox VE installation: You should have Proxmox installed and configured on your host.
  2. Ansible setup: Install Ansible on a machine that can communicate with your Proxmox host over SSH.

Step 1: Configure Ansible Inventory

To manage your Proxmox host, you need to configure your Ansible inventory file to include the Proxmox server. This file should contain the API URL and the authentication details (API token).

Here’s an example of an inventory file (inventory.yaml):

all:
  hosts:
    proxmox:
      ansible_host: 'your-proxmox-server-ip'
      ansible_user: 'root'

Replace your-proxmox-server-ip with your actual Proxmox details.

Step 2: Building our playbook

Now we need to decide what to do with Proxmox. In my case, I have the following:

---
- name: "Proxmox Playbook"
  hosts: proxmox
  serial: 1   # to avoid updating / breaking everything all at once
  become: true
  become_method: sudo

  vars:
    - librenms_agent_snmp_user: snmpuser
    - librenms_agent_snmp_password: snmppass
    - librenms_agent_snmp_encryption: snmppass
    - librenms_agent_snmp_syslocation: "your location"
    - librenms_agent_snmp_syscontact: your@email

  pre_tasks:

    - name: Update and Upgrade Aptitude Packages
      ansible.builtin.apt:
        update_cache: true
        upgrade: true
        cache_valid_time: 86400 # One day

    - name: "Install supporting packages"
      ansible.builtin.apt:
        pkg:
          - mailutils
          - sudo
          - acl
          - tcpdump
          - traceroute
          - net-tools
          - snmpd
          - lm-sensors
          - git
          - lldpd
          - p7zip-full
          - numactl
          - postfix
          - frr
          - frr-pythontools
          - ifupdown2
          - libpve-network-perl
          - rsyslog
          - corosync-qdevice
        state: present

  roles:
    - role: tobias_richter.librenms_agent
      librenms_agent_snmp_extensions:
        - name: osupdate
          script: osupdate
          comment: enable os updates in librenms
        - name: ".1.3.6.1.4.1.2021.7890.1 distro"
          script: distro
          comment: enable distribution
        - name: ntp-client
          script: ntp-client
          comment: enable ntp-client stats for librenms
      librenms_agent_check_mk_extensions:
        - script: dpkg
        - script: proxmox
      tags: librenmsagent

    - role: ironicbadger.proxmox-nag-removal
      tags: roleremoval
      notify: Update Apt

  post_tasks:
    - name: Enable mail relaying
      lineinfile: >
        dest=/etc/postfix/main.cf
        regexp="(?i)^\\s*relayhost\\b"
        insertafter="(?i)^#\\s*relayhost\\b"
        line="relayhost = 10.10.0.11"
      notify: Reload Postfix

    - include_tasks: strongswan-tasks.yaml
    - include_tasks: dnsmasq-tasks.yaml
    - include_tasks: gpsd-tasks.yaml
    - include_tasks: chrony-tasks.yaml

  handlers:

    - name: Update Apt
      ansible.builtin.apt:
        update_cache: true
        upgrade: true
        cache_valid_time: 86400 # One day

    - name: Reload Postfix
      service:
        name: postfix
        state: reloaded

    - name: Restart GPSD
      service:
        name: gpsd
        state: restarted

    - name: Restart chronyd
      service:
        name: chronyd
        state: restarted

The Pre-task section is run before a role is invoked, and the post-tasks aferwards. I start by updating the packages on the host and the cache, then installing all the packages I want by default. I install other packages as part of tasks, although they may not be part of a standard deployment.

The roles in this case installs and configures librenms' agent on the proxmox host, as well as disable the subscription nag screen.

The included tasks do the following:

strongswan-tasks.yaml - setup and configure strongswan for IPSEC when using VXLAN
dnsmasq-tasks.yaml - DHCP service for SDN Networks
gpsd-tasks.yaml - Configuration of my GPS recievers
chrony-tasks.yaml - Configuration of chrony to use the GPS and other hosts.

Using includes means I break up the config so it is easier to read, and also so I can reuse chunks. The strongswan-tasks.yaml looks like this:

---
    - name: "Install supporting packages"
      ansible.builtin.apt:
        pkg:
          - strongswan
        state: present

# Copy over config file
    - name: Copy ipsec config file
      ansible.builtin.copy:
        src: templates/ipsec.conf
        dest: /etc/ipsec.conf
        owner: root
        group: root
        mode: '0644'

# Copy over the secrets
    - name: Copy ipsec config file
      ansible.builtin.copy:
        src: templates/ipsec.secrets
        dest: /etc/ipsec.secrets
        owner: root
        group: root
        mode: '0600'

You will need to set up a directory called templates with ipsec.conf file and ipsec.secrets. The guide for what these files should look like can be found here. Setting up DNSMasq is even easier at the moment. This file looks like:

---
    - name: "Install supporting packages"
      ansible.builtin.apt:
        pkg:
          - dnsmasq
        state: present

    - name: "Disable DNSMasq"
      service:
        name: "dnsmasq"
        state: stopped
        enabled: false

Next comes gpsd-tasks. These are straight forward as well.

---
    - name: "Install supporting packages"
      ansible.builtin.apt:
        pkg:
          - gpsd
          - pps-tools
        state: present

    - name: Configure Devices
      lineinfile: 
        dest: /etc/default/gpsd
        search_string: "DEVICES="
        line: "DEVICES=/dev/ttyACM0"
      notify: Restart GPSD

    - name: Configure Devices
      lineinfile: 
        dest: /etc/default/gpsd
        search_string: "GPSD_OPTIONS="
        line: "GPSD_OPTIONS=\"-n -b -s 4800\""
      notify: Restart GPSD

    - name: Configure USBAUTO
      lineinfile: 
        dest: /etc/default/gpsd
        search_string: "USBAUTO="
        line: "USBAUTO=\"true\""
      notify: Restart GPSD

Note that any changes to the configuration trigger the notification to "Restart GPSD". These call the defined handlers, so that the service isn't constantly restarted when making changes. Lastly, I configure chrony:

---
    - name: "Install supporting packages"
      ansible.builtin.apt:
        pkg:
          - chrony
        state: present

    # Disable the debian pool.

    - name: Disable debian pool
      replace: 
        dest: /etc/chrony/chrony.conf
        regexp: '^pool'
        replace: "#pool "
      notify: Restart chronyd

    # Add local network sources

    - name: Add PVE Time Source - Firewall
      lineinfile: 
        dest: /etc/chrony/sources.d/firewall.sources
        line: "peer 192.168.0.1 iburst"
        create: yes
      notify: Restart chronyd

       # Add the GPS based sources

    - name: Add PVE Time Source - LocalGPS
      lineinfile: 
        dest: /etc/chrony/conf.d/GPS-01.conf
        line: "refclock SHM 0 refid GPS precision 1e-1 offset 0.128 delay 0.2"
        create: yes
      notify: Restart chronyd

    # Fix who is allowed to query the service.

    - name: Allow Localhost
      lineinfile: 
        dest: /etc/chrony/conf.d/allow-localhost.conf
        line: "allow 127.0.0.0/8"
        create: yes
      notify: Restart chronyd

    - name: Allow Lan
      lineinfile: 
        dest: /etc/chrony/conf.d/allow-lan.conf
        line: "allow 192.168.42.0/24"
        create: yes
      notify: Restart chronyd

Lastly, the handlers are defined to perform the restarts or updates when needed.

Alternatively, the code can be found on my github here.

Step 3: Running Playbooks

After writing the playbook, you can run it with the following command:

ansible-playbook -i inventory.yml proxmox-initial.yaml

This will trigger Ansible to connect to your Proxmox host and execute the actions described in the playbook.

You can extend your Ansible playbooks as needed, integrating Proxmox management into your overall automation workflow.

Conclusion

Using Ansible to manage your Proxmox host and its resources allows for efficient, repeatable, and scalable operations. Ansible’s flexibility makes it an excellent choice for automating tasks in your Proxmox environment.

With a little setup and a few playbooks, you can drastically simplify the management of your Proxmox infrastructure, allowing you to focus on more critical tasks and improve overall efficiency.

Happy automating!

As an Amazon Associate I earn from qualifying purchases.

If you have found this post useful, please consider donating.