В этой статья я покажу как с помощью языка программирования Python 3 и модуля Proxmoxer работать с API Proxmox.
Подготовка к работе
Я, для того чтобы подключиться к своему Proxmox VE кластеру, на нём же создаю специальный контейнер для управления. Создаю я его из шаблона Devuan, чтобы он занимал как можно меньше памяти. Кластер Proxmox VE у меня версии 6.4.
Создание самого контейнера на Proxmox VE я описывать не буду. Просто выберите шаблон для контейнера devuan-3.0-standard_3.0_amd64.tar.gz.
- Систему Devuan и её установку я описывал в этой статье.
После разворачивания контейнера я подключаюсь к нему и обновляю:
# apt update # apt dist-upgrade
Чтобы работало автоматическое дополнение в bash, установим bash-completion и пропишем его в .bashrc:
# apt install bash-completion # nano .bashrc . /etc/bash_completion
После обновления и настройки bash_completion, перезагрузимся:
# reboot
Опять подключимся к контейнеру и установим верный часовой пояс:
# dpkg-reconfigure tzdata
И установим необходимый язык в системе, для чего вначале установим пакет locales:
# apt install locales # dpkg-reconfigure locales
Установка python 3 и необходимых библиотек
Установим python 3 и pip 3:
# apt install python3 # apt install python3-pip
Добавим библиотеки: proxmoxer и requests:
# pip3 install proxmoxer # pip3 install requests
Подключение к кластеру
Так как я не программист на Python, а системный администратор, возможно задачи, описанные в статье, можно решить оптимальнее. В этой статье я демонстрирую, как с помощью языка программирования Python 3 получить информацию о нодах, контейнерах и виртуальных машинах кластера ProxmoxVE.
И так, начинаем писать скрипт на python, я для этого использую nano, вы можете использовать любой другой текстовый редактор.
Для того чтобы подключиться к кластеру PVE, импортируйте библиотеку proxmoxer и используйте указанную строку подключения:
from proxmoxer import ProxmoxAPI proxmox = ProxmoxAPI('192.168.1.10', user='root@pam', password='Password', verify_ssl=False, service='PVE')
В примере выше 192.168.1.10 — это IP-адрес одной из нод в кластере, Password — пароль этой ноды. Теперь используя переменную proxmox, вы сможете взаимодействовать с вашим кластером.
Список нод в кластере
Если, вдруг, вы не знаете что такое нода (node / узел), то это один из серверов ProxmoxVE в кластере. Proxmox VE позволяет управлять всем кластером подключившись к любой ноде этого кластера.
Чтобы проверить подключение, выведем список нод в нашем кластере. Для этого запишем следующий код:
for node in proxmox.nodes.get(): print(node)
Сохраним файл и проверим выполнение скрипта:
# python3 pve.py {'maxcpu': 4, 'maxmem': 33635913728, 'type': 'node', 'status': 'online', 'maxdisk': 58861158400, 'level': '', 'ssl_fingerprint': 'E6:81:8D:19:3C:0E:F2:94:72:D1:B9:F3:6C:B7:96:A8:E8:25:29:1E:BC:43:22:08:0B:71:CC:B5:E6:D0:12:72', 'uptime': 1190821, 'node': 'pve-1', 'disk': 3782868992, 'mem': 20615454720, 'id': 'node/pve-1', 'cpu': 0.195016909399431} {'status': 'online', 'type': 'node', 'maxdisk': 193529643008, 'ssl_fingerprint': 'A5:BB:44:43:6B:B8:51:1E:4D:2B:15:1A:6F:F9:E3:85:9E:14:C2:A7:96:94:24:A9:B8:57:85:A9:CF:0C:BA:09', 'level': '', 'maxmem': 270211153920, 'maxcpu': 16, 'cpu': 0.130895213695818, 'mem': 113991684096, 'id': 'node/pve-2', 'uptime': 1002471, 'node': 'pve-2', 'disk': 3808034816} {'node': 'pve-3', 'status': 'offline', 'type': 'node', 'ssl_fingerprint': '7D:19:9D:96:1D:1E:EA:AB:4F:2A:D1:35:2B:29:1F:F7:E4:2C:80:1F:8D:6B:43:DE:1E:06:6A:57:EC:22:41:B7', 'id': 'node/pve-3'}
Мы получили, в виде словаря, информацию по трём нодам. При этом, одна из нод (pve-3) сейчас отключена (‘status’: ‘offline’) и по ней мы получаем меньше информации, например не получаем её uptime.
Используя словари, мы можем вытащить определённую информацию, например название ноды и её статус:
for node in proxmox.nodes.get(): print(node['node'], node['status'])
Сохраним скрипт и выполним его:
# python3 pve.py pve-1 online pve-3 offline pve-2 online
А чтобы пробегаться только по включенным нодам, можно добавить следующую проверку:
for node in proxmox.nodes.get(): if node['status'] == 'online': print(node['node'], node['status'])
Сохраним скрипт и выполним его:
# python3 pve.py pve-1 online pve-2 online
Список контейнеров
Теперь я покажу, как можно пробежаться по всем контейнерам в кластере.
Кстати, проверять включена или выключена нода в этом случае необходимо. Так как вы получите ошибку при попытке получить информацию о контейнере на выключенной ноде. Можно, конечно, обрабатывать ошибки используя «try: except:», но в этом случае лучше избежать ошибок.
Вот так можно получить информацию по контейнерам в кластере.
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): print(vm)
Выполним этот скрипт и получим следующую информацию:
# python3 pve.py {'status': 'stopped', 'diskread': 0, 'disk': 0, 'name': 'test-alpine', 'template': '', 'uptime': 0, 'maxdisk': 17179869184, 'maxmem': 536870912, 'vmid': '100', 'type': 'lxc', 'cpus': 2, 'cpu': 0, 'maxswap': 536870912, 'mem': 0, 'diskwrite': 0, 'netout': 0, 'netin': 0, 'swap': 0} {'template': '', 'name': 'proxmoxer', 'uptime': 4331509, 'maxdisk': '8589934592', 'disk': '693633024', 'diskread': 0, 'status': 'running', 'pid': '14189', 'netin': 2367693008, 'swap': 4096, 'diskwrite': 0, 'netout': 1471053185, 'cpus': 1, 'cpu': 0.000159274206402869, 'maxswap': 268435456, 'mem': 43651072, 'maxmem': 268435456, 'vmid': '101', 'type': 'lxc'} {'maxdisk': 8589934592, 'uptime': 0, 'template': '', 'name': 'test-devuan', 'disk': 0, 'diskread': 0, 'status': 'stopped', 'swap': 0, 'netin': 0, 'netout': 0, 'diskwrite': 0, 'mem': 0, 'maxswap': 1073741824, 'cpu': 0, 'cpus': 2, 'type': 'lxc', 'vmid': '104', 'maxmem': 4294967296}
Мы получили, в виде словарей, информацию по контейнерам. Можем вытащить лишь определённую информацию:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): print(vm['vmid'], vm['name'], vm['status'])
Выполним этот скрипт:
# python3 pve.py 100 test-alpine stopped 101 proxmoxer running 104 test-devuan stopped
Но, не всю информацию можно получить по выключенным контейнерам, например pid иди uptime нельзя получить (так как их и не может существовать).
Чтобы получать по включенным контейнерам больше информации, а по выключенным меньше, поправим наш скрипт:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid'])
Выполним скрипт:
# python3 pve.py 100 test-alpine stopped 101 proxmoxer running pid=14189 104 test-devuan stopped
Список виртуальных машин
Здесь всё аналогично предыдущему пункту. Просто поменяйте lxc на qemy:
for vm in proxmox.nodes(node['node']).qemu.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid'])
Список контейнеров и виртуальных машин
Объединив два скрипта в один цикл, мы можем получить информацию по всем контейнерам и виртуальным машинам в кластере PVE:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) for vm in proxmox.nodes(node['node']).qemu.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid'])
Выполнив его получим следующую информацию:
# python3 pve.py 100 test-alpine stopped 101 proxmoxer running pid=14189 104 test-devuan stopped 106 debian-10 running pid=29463 103 router running pid=17719 107 win-rdp stopped
Конфиги контейнеров и виртуальных машин
Предыдущим способом мы не получаем полную информации. Например, не можем получить список виртуальны сетевых интерфейсов в контейнерах или виртуальных машинах.
Чтобы спуститься на уровень конфигов нужно использовать следующий код:
# для контейнеров proxmox.nodes(node['node']).lxc(vm['vmid']).config().get() # для виртуальных машин proxmox.nodes(node['node']).qemu(vm['vmid']).config().get()
Например, добавим эти строки в наш предыдущий скрипт:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) print(proxmox.nodes(node['node']).lxc(vm['vmid']).config().get()) for vm in proxmox.nodes(node['node']).qemu.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) print(proxmox.nodes(node['node']).qemu(vm['vmid']).config().get())
Выполним скрипт и получим:
$ python3 pve.py 100 test-alpine stopped {'hostname': 'test-alpine', 'net0': 'name=eth0,bridge=vmbr0,firewall=1,hwaddr=3A:9C:11:BB:2D:C5,ip=dhcp,tag=425,type=veth', 'digest': 'f00d3db9f8366452dd69d1b0775a447d9d2208f7', 'unprivileged': 1, 'swap': 512, 'memory': 512, 'rootfs': 'data:subvol-100-disk-0,mountoptions=noatime,size=8G', 'arch': 'amd64', 'cores': 2, 'ostype': 'alpine'} 101 proxmoxer running pid=14189 {'rootfs': 'data:subvol-101-disk-0,size=8G', 'protection': 1, 'ostype': 'debian', 'cores': 1, 'arch': 'amd64', 'onboot': 1, 'nameserver': '192.168.1.1', 'searchdomain': 'test.domain', 'hostname': 'proxmoxer', 'net0': 'name=eth0,bridge=vmbr0,firewall=1,hwaddr=3B:4C:17:BB:2D:C5,ip=dhcp,tag=425,type=veth', 'swap': 512, 'memory': 512, 'digest': '634eb485d2e8e108148e488fac424143bb360216'} 104 test-devuan stopped {'unprivileged': 1, 'digest': '9efa9c99f90a0e740123d43de7623f8b4f8dd454', 'memory': 4096, 'swap': 1024, 'net0': 'name=eth0,bridge=vmbr0,firewall=1,hwaddr=9A:39:4F:1B:F8:CC,ip=192.168.1.52,tag=425,type=veth', 'hostname': 'test-devuan', 'arch': 'amd64', 'cores': 2, 'ostype': 'devuan', 'rootfs': 'data:subvol-104-disk-0,size=8G'} 106 debian-10 running pid=29463 {'numa': 0, 'ide2': 'none,media=cdrom', 'ostype': 'l26', 'cores': 2, 'scsi0': 'data:vm-106-disk-0,size=20G,ssd=1', 'smbios1': 'uuid=f2371704-954a-4fa4-98ef-d5bc568e2be7', 'sockets': 1, 'net0': 'virtio=46:8D:56:9A:6A:4F,bridge=vmbr0,firewall=1,tag=425', 'scsihw': 'virtio-scsi-pci', 'name': 'debian-10', 'vmgenid': 'b62acef5-8d59-4384-b512-ca1d3cb3354f', 'memory': 2048, 'boot': 'order=scsi0;ide2;net0', 'digest': '3eb7cca296d4be60551406fc64cc8156ca39f140', 'agent': '1'} 103 router running pid=17719 {'smbios1': 'uuid=e530b851-c21a-4d87-9e9e-131d745c8f3f', 'net0': 'virtio=16:20:F0:59:B4:A9,bridge=vmbr0,tag=425', 'net3': 'virtio=34:5F:FD:C7:CD:3D,bridge=vmbr0,tag=65', 'name': 'router', 'boot': 'order=ide0', 'digest': '51ad9bd07bab471177b16b45ca9fb657795f2a82', 'numa': 0, 'cores': 4, 'sockets': 1, 'vmgenid': '52cba466-5f52-4c86-89fa-0bc1d4fa394c', 'ide0': 'data:vm-103-disk-0,size=1G', 'onboot': 1} 107 win-rdp stopped {'virtio0': 'data:vm-107-disk-0,size=100G', 'agent': '1', 'sockets': 1, 'scsihw': 'virtio-scsi-pci', 'vmgenid': 'd71cf144-1d77-4828-8303-8bf514206222', 'machine': 'pc-q35-5.2', 'onboot': 1, 'protection': 1, 'memory': 8192, 'boot': 'order=virtio0;ide2;net0', 'digest': 'c0345c5481166cb24a187308f92abb2255c195f0', 'smbios1': 'uuid=784ceaaf-2651-4a44-ab52-e787f0c08dd5', 'net0': 'virtio=46:5E:BE:4C:3C:24,bridge=vmbr0,firewall=1,tag=425', 'name': 'win-rdp', 'ostype': 'win10', 'cores': 4, 'numa': 0, 'ide2': 'none,media=cdrom'}
А чтобы вытащить определённую информацию можно прибегнуть к следующему приёму. Словарь запихнём в переменную и из переменной будем вытаскивать информацию:
config = proxmox.nodes(node['node']).lxc(vm['vmid']).config().get() print(config['net0']) config = proxmox.nodes(node['node']).qemu(vm['vmid']).config().get() print(config['net0'])
Применим это к нашему скрипту:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) config = proxmox.nodes(node['node']).lxc(vm['vmid']).config().get() print(config['net0']) for vm in proxmox.nodes(node['node']).qemu.get(): if vm['status'] == 'stopped': print(config['net0']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) print(vm['vmid'], vm['name'], vm['status']) config = proxmox.nodes(node['node']).qemu(vm['vmid']).config().get()
Выполнив этот скрипт, получим следующую информацию:
# python3 pve.py 100 test-alpine stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=3A:9C:11:BB:2D:C5,ip=dhcp,tag=425,type=veth 101 proxmoxer running pid=14189 name=eth0,bridge=vmbr0,firewall=1,hwaddr=3B:4C:17:BB:2D:C5,ip=dhcp,tag=425,type=veth 104 test-devuan stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=9A:39:4F:1B:F8:CC,ip=192.168.1.52,tag=425,type=veth 100 g-test-alpine stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=3E:9C:11:BB:2D:F5,ip=dhcp,tag=555,type=veth 106 debian-10 running pid=29463 virtio=46:8D:56:9A:6A:4F,bridge=vmbr0,firewall=1,tag=425 103 router running pid=17719 virtio=16:20:F0:59:B4:A9,bridge=vmbr0,tag=425 107 win-rdp stopped virtio=46:5E:BE:4C:3C:24,bridge=vmbr0,firewall=1,tag=425
Но, у некоторых контейнеров и виртуальных машин есть несколько сетевых карт. Существует возможность пробежаться по всем сетевым картам в цикле. Но придется обрабатывать ошибки, чтобы скрипт продолжил работать в случае отсутствия сетевой карты у виртуальной машины.
Для этой задачи я использую следующий приём, он не оптимален, и возможно кто-то из вас придумает как его улучшить. Я предполагаю что у виртуальной машины не может быть больше 10 сетевых карт. Каждая сетевая карта называется net[N], где N это число от 0. Поэтому я пробегаюсь по сетевым картам следующим способом:
i = 0 while i < 10: try: print(config['net' + str(i)]) except: break i = i + 1
Правильнее было бы узнать количество сетевых интерфейсов и пробегаться только по ним. И, вдруг, у какой-нибудь виртуальной машины окажется больше 10 сетевых интерфейсов, тогда мы получим не всю информацию.
Добавив эту конструкцию в наш скрипт, и получим:
for node in proxmox.nodes.get(): if node['status'] == 'online': for vm in proxmox.nodes(node['node']).lxc.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) config = proxmox.nodes(node['node']).lxc(vm['vmid']).config().get() i = 0 while i < 10: try: print(config['net' + str(i)]) except: break i = i + 1 for vm in proxmox.nodes(node['node']).qemu.get(): if vm['status'] == 'stopped': print(vm['vmid'], vm['name'], vm['status']) else: print(vm['vmid'], vm['name'], vm['status'], "pid=" + vm['pid']) config = proxmox.nodes(node['node']).qemu(vm['vmid']).config().get() i = 0 while i < 10: try: print(config['net' + str(i)]) except: break i = i + 1
Выполнив этот скрипт получим следующую информацию:
# python3 pve.py 100 test-alpine stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=3A:9C:11:BB:2D:C5,ip=dhcp,tag=425,type=veth 101 proxmoxer running pid=14189 name=eth0,bridge=vmbr0,firewall=1,hwaddr=3B:4C:17:BB:2D:C5,ip=dhcp,tag=425,type=veth 104 test-devuan stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=9A:39:4F:1B:F8:CC,ip=192.168.1.52,tag=425,type=veth 100 g-test-alpine stopped name=eth0,bridge=vmbr0,firewall=1,hwaddr=3E:9C:11:BB:2D:F5,ip=dhcp,tag=555,type=veth 106 debian-10 running pid=29463 virtio=46:8D:56:9A:6A:4F,bridge=vmbr0,firewall=1,tag=425 103 router running pid=17719 virtio=16:20:F0:59:B4:A9,bridge=vmbr0,tag=425 virtio=34:5F:FD:C7:CD:3D,bridge=vmbr0,tag=65 107 win-rdp stopped virtio=46:5E:BE:4C:3C:24,bridge=vmbr0,firewall=1,tag=425
Создание контейнера
Отдельно я вам покажу, как создать контейнер с помощью кода. Это делается следующим способом:
node = proxmox.nodes('pve-1') node.lxc.create(vmid=202, ostemplate='local:vztmpl/devuan-3.0-standard_3.0_amd64.tar.gz', hostname='test-dev3', storage='local', cores=2, memory=1024, swap=1024, net0='name=eth0,bridge=vmbr0,firewall=1,tag=425,ip=dhcp', password='secret')
Дополнительная информация
- Proxmoxer на GitHub найдёте здесь. К сожалению, там примеры для старой версии Proxmox (когда ещё использовался openvz, вместо lxc)
- Документация по Proxmox API