Wer gerne seine Linux VMs in Proxmox aktuell hält, dass aber von Hand machen muss, der hat ganzschön etwas zu tun. Alle VMs hochfahren, einen Snapshot erstellen, patchen und wieder herunterfahren. Zeit für ein kleines Ansible Playbook, dass uns diese Aufgabe abnimmt.
Gleich vorweg, wer einfach nur mein Playbook nutzen möchte ohne sich jetzt in Ansible und die Proxmox REST API einzuarbeiten, der kann sich das Playbook hier herunterladen und gerne verwenden.
Das Playbook läuft in zwei Phasen ab. Zum einen wird zunächst nur auf dem “localhost” (also dem Ansible Execution Node, dort von wo wir das Playbook starten) ausgeführt. Später, wenn es um das tatsächliche Patchen der VM geht, wird eine Verbindung zu der VM hergestellt. Daraus ergibt sich, dass wir das Ansible Inventory, mit dem wir das Playbook starten entsprechend mit allen VMs befüllen müssen als auch den localhost. Hierbei ist besonders die Groß- und Kleinschreibung zu beachten. Der Name im Inventory und in Proxmox müssen identisch sein. Hier ein Beispiel eines Inventories:
[Patching:vars]
ansible_user=username
ansible_password='SuperSecretPassword'
ansible_become_user=root
ansible_become_pass='SuperSecretPassword'
[Patching]
vmsrvansibletest1
vmsrvansibletest2
localhost ansible_python_interpreter=/usr/bin/python3
Die Variablen für die Gruppe “Patching” regeln, mit welchem Betriebssystembenutzer und Passwort versucht sich anzumelden. Da die Patchinstallation eine Aufgabe für den root Benutzer ist, müssen wir ebenfalls das sogenannte “Become Passwort” (sudo Passwort) angeben. Alternativ können wir natürlich auch das Playbook mit den Parametern -k und -K aufrufen. Dann werden wir beim Starten des Playbooks nach beiden Passwörtern gefragt.
Nun schauen wir uns das eigentliche Playbook an, dass in der Datei update_proxmox_vm.yml zu finden ist.
---
# Version 1.1.0
- hosts: "localhost"
vars:
proxmox_api_host: "pvehost1"
proxmox_api_user: "root@pam"
proxmox_api_password: "SuperSecretPassword"
proxmox_api_port: "8006"
boot_time: 180
vm_list:
- vm_name: myShinyVM1
- vm_name: myShinyVM2
snapshot: true
tasks:
- name: Logon to Proxmox Server
uri:
method: POST
validate_certs: no
return_content: yes
url: "https://{{ proxmox_api_host }}:8006/api2/json/access/ticket"
body_format: json
body:
username: "{{ proxmox_api_user }}"
password: "{{ proxmox_api_password }}"
register: proxmox_session
become: false
- name: parse cookie data
set_fact:
proxmox_api_cookie:
Cookie: "PVEAuthCookie={{ proxmox_session.json.data.ticket }}"
CSRFPreventionToken: "{{ proxmox_session.json.data.CSRFPreventionToken }}"
no_log: true
- name: get information about all vms in the cluster
uri:
method: GET
validate_certs: no
url: "https://{{ proxmox_api_host }}:8006/api2/json/cluster/resources?type=vm"
headers: "{{ proxmox_api_cookie }}"
register: proxmox_cluster_information
- name: Patch VM
include_tasks: patch_vm.yml
loop: "{{ vm_list }}"
Die Variablen die hier am Anfang definiert sind, müssen auf eure Gegebenheiten angepasst werden. Ihr könnt das entweder tun, indem ihr das Playbook editiert und z.B. den Proxmox Server (proxmox_api_host) und den Proxmox API Benutzer (proxmox_api_user) und dessen Passwort dort eingebt, oder ihr übergebt die Variablen als “Extra Variablen” oder verschlüsselt in einem Ansible Vault. Hier gehen wir davon aus, dass ihr die erforderlichen Informationen im Playbook anpasst.
Weiterhin müsst ihr eine Liste an VMs die ihr patchen wollt in der vordefinierten Liste vm_list angeben. Hier könnt ihr ebenfalls je VM definieren, ob im Rahmen des Patching im Vorfeld ein Snapshot erstellt wird (snapshot: true). In unserem Beispiel würde die Liste so aussehen:
vm_list:
- vm_name: vmsrvansibletest1
- vm_name: vmsrvansibletest2
snapshot: true
Damit würde für die VM “vmsrvansibletest2” ein Snapshot erstellt werden, für die VM “vmsrvansibletest1” jedoch nicht.
Das Play selbst wird damit beginnen, sich an der Proxmox API anzumelden und einen “Access Token” zu bekommen. Anschließend wird eine Liste mit allen VMs (keine LX-Container!) abgefragt. Nun wird ein weiteres “Unter”-Playbook patch_vm.yml aufgerufen und zwar für jede VM in der Liste vm_list.
Das Playbook patch_vm.yml sieht folgendermaßen aus:
---
- name: Update VM
block:
- name: Extract Node and VM ID for VM {{ item.vm_name }}
set_fact:
vm_id: "{{ proxmox_cluster_information.json.data | selectattr('name', 'equalto', item.vm_name) | map(attribute='vmid') | first }}"
vm_node: "{{ proxmox_cluster_information.json.data | selectattr('name', 'equalto', item.vm_name) | map(attribute='node') | first }}"
- name: Get VM Status
uri:
method: GET
validate_certs: no
url: "https://{{ proxmox_api_host }}:{{ proxmox_api_port }}/api2/json/nodes/{{ vm_node }}/qemu/{{ vm_id }}/status/current"
headers: "{{ proxmox_api_cookie }}"
register: vm_status
- name: Start VM {{ item.vm_name }}
uri:
method: POST
validate_certs: no
url: "https://{{ proxmox_api_host }}:{{ proxmox_api_port }}/api2/json/nodes/{{ vm_node }}/qemu/{{ vm_id }}/status/start"
headers: "{{ proxmox_api_cookie }}"
body_format: form-urlencoded
body:
timeout: "{{ boot_time }}"
when: vm_status.json.data.status == 'stopped'
- name: Waiting for the VM to boot up
pause:
seconds: "{{ boot_time }}"
when: vm_status.json.data.status == 'stopped'
- name: Gather VM Facts
gather_facts:
register: vm_facts
delegate_to: "{{ item.vm_name }}"
- name: Take a VM Snapshot of VM {{ item.vm_name }}
uri:
method: POST
validate_certs: no
url: "https://{{ proxmox_api_host }}:{{ proxmox_api_port }}/api2/json/nodes/{{ vm_node }}/qemu/{{ vm_id }}/snapshot"
headers: "{{ proxmox_api_cookie }}"
body_format: form-urlencoded
body:
snapname: "snap_before_patch-{{ ansible_date_time.date }}"
description: "Snapshot taken by Update Automation"
vmstate: 1
when: (item.snapshot|default(false))
- name: Waiting for the VM to finish Snapshot
pause:
seconds: "{{ boot_time }}"
when: (item.snapshot|default(false))
- name: Update VM {{ item.vm_name }} with apt
apt:
force_apt_get: yes
name: "*"
state: latest
update_cache: yes
become: yes
delegate_to: "{{ item.vm_name }}"
when: vm_facts.ansible_facts.ansible_os_family == 'Debian'
- name: Update VM {{ item.vm_name }} with yum
yum:
name: "*"
state: latest
update_cache: yes
become: yes
delegate_to: "{{ item.vm_name }}"
when: vm_facts.ansible_facts.ansible_os_family == 'RedHat'
- name: Shutdown VM {{ item.vm_name }} when it was stopped before patching
uri:
method: POST
validate_certs: no
url: "https://{{ proxmox_api_host }}:{{ proxmox_api_port }}/api2/json/nodes/{{ vm_node }}/qemu/{{ vm_id }}/status/shutdown"
headers: "{{ proxmox_api_cookie }}"
body_format: form-urlencoded
body:
forceStop: 1
when: vm_status.json.data.status == 'stopped'
rescue:
- debug:
msg: "There was an Error during Patch Installation on {{ item.vm_name }}. Maybe you misspelled the VM Name. Be aware, that Proxmox VM Names are case sensitive!"
Mit dem Namen der VM wird nun die VM ID aus Proxmox ausgelesen. Nun wird geprüft, ob die VM überhaupt angeschaltet ist. Diesen Status merken wir uns in der Variable vm_status. Abhängig davon wird die VM gestartet und ein Snapshot durchgeführt (wenn wir das angegeben haben).
Nach dem Hochfahren der VM geht es ans eigentliche Patchen des Betriebssystems. War die VM vor dem Patchen gestoppt, wird sie wieder heruntergefahren und die nächste VM ist an der Reihe. Sollte es irgendwo innerhalb des patch_vm.yml Playbooks zu einem Fehler kommen, wird die VM übersprungen und mit der nächsten VM weitergemacht.
Wenn wir alle Variablen angepasst haben, können wir das Playbook mit folgendem Befehl starten:
ansible-playbook -i hosts update_proxmox_vm.yml
Wenn wir die Liste der VMs die es zu patchen gilt nicht im Playbook direkt hinterlegen wollen, können wir auch eine Liste als “Extra Variable” in einer Datei angeben. Das sieht dann so aus:
ansible-playbook -i hosts -e "@vm_list_to_patch.yml" -e '{"proxmox_api_host": "srvoffice2.home.lab", "proxmox_api_password": "Evenmoresecret"}' update_proxmox_vm.yml
Wenn das Play fehlerfrei funktioniert, hindert uns niemand daran, es regelmäßig auszuführen. Richtig schön wird es, wenn wir das Playbook über einen Scheduler in AWX (z.B. wöchentlich) ausführen. Dann brauchen wir wirklich nie mehr einen Gedanken an das Thema Patchen zu verschwenden.
Philip