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
Hallo Philip,
danke für die tollen Scripts. Ich versuche das ganze über Semaphore anzulegen und scheitere da bei Environment (“Extra variables must be valid JSON”).
Kannst du da mit einer Anleitung auf die Sprünge helfen?
Gruß
Hallo,
welche Extra Variablen übergibst du an die Rolle und in welcher Form?
Gruß Philip
Ich dachte ich packe die Liste mit den VMs in einer extra Datei (das wäre dann doch das Environment).
Dementsprechend versuche ich, diesen Block da rein zu packen:
vm_list:
– vm_name: vmsrvansibletest1
– vm_name: vmsrvansibletest2
snapshot: true
Dort meckert er dann wegen dem JSON-Format.
Ich muss an der Stelle erwähnen, dass ich ein absoluter Newbie bin was Ansible angeht. Nutzt du selbst Semaphore?
Gruß
Florian
Hallo Florian,
nein, in dem Fall musst du die Liste der VMs als Extra Variablen übergeben. Die Extra Variablen müssen dennoch valides JSON sein (was es bei dir nicht ist).
Folgendes Beispiel funktioniert als Environment => Extra Vars:
{
"boot_time": 60,
"proxmox_api_host": "192.168.2.123",
"proxmox_api_user": "root@pam",
"proxmox_api_password": "Supersecret",
"vm_list": [
{
"vm_name": "srvtest0"
},
{
"vm_name": "srvtest1"
}
]
}
Bitte beachte, dass die Liste der VMs case sensitive ist zu den VM Namen, wie du sie in Proxmox vergeben hast UND das Ansible sein Inventory ebenfalls case sensitive verwaltet. Das heißt, die Namen der VMs und Hosts im Inventory müssen identisch sein.
Gruß Philip
Hallo Philip,
danke für deine schnelle Antworten.
Leider komme ich mit Semaphore noch nicht so wirklich klar und beschäftige mich auch zum ersten mal mit Ansible.
Nutzt du zufällig auch Semaphore oder machst du alles per SSH?
Falls du Semaphore nutzt, wäre ich sehr an Screenshots interessiert, ich verstehe gerade nur Bahnhof.
Gerne auch per Mail.
Gruß
Florian
Hallo Florian,
ich habe dir soeben eine Mail geschickt mit einer Anleitung (zumindest habe ich es versucht). Um deine Frage zu beantworten, nein, ich verwendet kein Semaphore. Ich verwende AWX für die Verwaltung meiner Ansible Rollen / Playbooks und meines Homelabs. Semaphore halte ich für den Einstieg aber für eine gute Gelegenheit. Kann bei weitem nicht soviel wie AWX, aber ist deutlich einfacher zu handhaben.
Gruß Philip