В этой статья я покажу как с помощью языка программирования Python 3 и модуля Proxmoxer работать с API Proxmox.

Подготовка к работе

Я, для того чтобы подключиться к своему Proxmox VE кластеру, на нём же создаю специальный контейнер для управления. Создаю я его из шаблона Devuan, чтобы он занимал как можно меньше памяти. Кластер Proxmox VE у меня версии 6.4.

  • О том, что такое Proxmox VE я писал в этой статье.
  • Процесс установки Proxmox VE я описывал здесь.

Создание самого контейнера на Proxmox VE я описывать не буду. Просто выберите шаблон для контейнера devuan-3.0-standard_3.0_amd64.tar.gz.

После разворачивания контейнера я подключаюсь к нему и обновляю:

# 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. Работа с Proxmox API используя Python
Имя статьи
Proxmoxer. Работа с Proxmox API используя Python
Описание
В этой статья я покажу как с помощью языка программирования Python 3 и модуля Proxmoxer рабо

4 Replies to “Proxmoxer. Работа с Proxmox API используя Python”

    1. Ну тут можно использовать просто утилиту pct. То есть pct start . А из скрипта, у меня, не было необходимости запускать вмки.

  1. Забыли подытожить в конце скрипта, как это не выводить на экран, а выгрузить это в файл?
    Например csv или xls или хотя бы в текст.
    Подскажите плиз.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *