Управление объектами Azure через Python

Опубликовано: 02.03.2021
Автор: Виталий Бочкарев
Поддержать автора статьи по этой ссылке

Для взаимодействия с облачной инфраструктурой Microsoft Azure компания Майкрософт поддерживает открытый проект на GitHub, который реализует Python SDK. С помощью библиотек из этого проекта можно реализовать взаимодействие с облачной инфраструктурой посредством скриптов или даже написать собственный облегченный веб-сервис для Azure.

В своей работе я использую такие модули, как

  • azure.core
  • azure.mgmt.compute
  • azure.mgmt.network
  • azure.mgmt.monitor

Чтобы установить эти модули для использования в своем проекте, необходимо перейти в папку проекта и выполнить команду

pipenv install "azure-identity==1.5.0" "azure-mgmt-compute==18.1.0" "azure-mgmt-network==17.1.0" "azure.mgmt.monitor==2.0.0"

Если эти модули необходимо установить для всей системы, то нужно выполнить команду

pip install "azure-identity==1.5.0" "azure-mgmt-compute==18.1.0" "azure-mgmt-network==17.1.0" "azure.mgmt.monitor==2.0.0"

Примечание. Версия модулей важна, так как от версии к версии некоторые функции внутри модулей меняются, поэтому проект с новыми, а тем более старыми версиями может вести себя некорректно или вообще не работать.

На основе этих модулей я написал несколько функций, которые я объединил в файл module_azu.py:

  • создание объекта с параметрами подключения,
  • получение свойств сервера из локального json-файла, который заранее был сформирован другой процедурой,
  • получение свойств сервера из локального json-файла, который заранее был сформирован другой процедурой, и дополнение этих данных свежими данными из портала,
  • запуск сервера,
  • остановка сервера,
  • получение метрик сервера.
print('--- importing module_azu.py ---')
from azure.identity import ClientSecretCredential
from azure.mgmt.compute import ComputeManagementClient
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.monitor import MonitorManagementClient
from azure.core.pipeline.policies import ProxyPolicy
from re import findall as re_findall
from datetime import datetime, timedelta
from os import path as os_path
from os import getcwd as os_getcwd
from os import environ as os_environ
from sys import path as sys_path

# импортирование модулей из проекта
# два метода импортирования необходимо для реализации разных типов запуска модуля:
# - из веб-сервера с Django-проектом
# - прямым запуском, но и использованием переменных из Django
try:
  # попытка импортировать модули по относительным путям
  print('  trying to import modules with relative paths')
  from .module_core import instances_json_read
  from django.conf import settings
  print('  relative import is OK')
except ImportError:
  # импортирование модулей по абсолютным путям
  print('Relative import failed')
  print('Trying to import modules with absolute paths')
  try:
    # получение текущей дирректории, откуда запущен скрипт - должен быть корень проекта
    current_directory = os_getcwd()
    # добавление пути к папке проекта в переменные среды
    sys_path.append(current_directory)
    # подключение настроек проекта
    os_environ.setdefault('DJANGO_SETTINGS_MODULE', 'webconsole.settings')
    # импортирование переменных Django в текущую среду
    from django.conf import settings
    from module_core import instances_json_read
    print('Absolute import is OK')
  except ModuleNotFoundError:
    print('Absolute import failed')
    exit()
print()


# функция для создания объекта с параметрами подключения
def get_credentials():
  from os import environ
  # установка переменных среды, так как клиент Azure игнорирует аргумент с настройками прокси
  for item in settings.PROXY_SETTINGS['proxies'].keys():
    environ[item + '_proxy'] = settings.PROXY_SETTINGS['proxies'][item]
  # установка параметров прокси подключения для клиента !!! не работает, закомментировано
  #azu_proxy_policy = ProxyPolicy()
  #azu_proxy_policy.proxies = settings.COMPANY_PROXIES
  # создание объекта с параметрами подключения
  credentials = ClientSecretCredential(
      tenant_id = settings.AZU_TENANT_ID,
      client_id = settings.AZU_CLIENT_ID,
      client_secret = settings.AZU_SECRET_KEY,
      use_env_settings = True,
      # !!! этот аргумент игнорируется функцией, закомментирован
      #proxies = COMPANY_PROXIES, 
  )
  # !!! этот параметр игнорируется функцией, закомментирован
  #credentials.proxy_policy = azu_proxy_policy
  # получение AZU_SUBSCRIPTION_ID из настроек Django
  subscription_id = settings.AZU_SUBSCRIPTION_ID
  # возврат объектов с параметрами подключения
  return credentials, subscription_id


# функция получения параметров сервера из локального json-файла, который подготовлен заранее другим модулем 
def azu_instance_properties_local(vm_name):
  #‭ ‬путь к json-файлу составляется из настроек Django
  instances_filepath = os_path.join(settings.BASE_DIR, settings.PROJECT_INSTANCES_JSON_FILEPATH)
  # запрос данных из json-файла
  try:
    # чтение таблицы из json-файла
    instances_table = instances_json_read(instances_filepath)['azu_vm_table']
    # поиск сервера в полученной таблице
    instance_line = instances_table.loc[vm_name.lower()].to_dict()
  except:
    instance_line = ''
  # возврат строки с параметрами сервера
  return instance_line


# функция получения параметров сервера из локального json-файла, дополнение свежими данными из портала 
def azu_instance_properties_mixed(vm_name):
  # получение локальных данных из файла
  vm_properties = azu_instance_properties_local(vm_name)
  # получение облачных данных
  credentials, subscription_id = get_credentials()
  compute_client = ComputeManagementClient(credentials, subscription_id)
  vm_item = compute_client.virtual_machines.get(vm_properties['rgroup'], vm_name, expand='instanceView')
  # установка id сервера как его имя в нижнем регистре
  i_id = vm_item.name.lower()
  # сохранение тэгов в отдельный словарь
  tags = {}
  for item in vm_item.tags.keys():
    tags.update({item: vm_item.tags[item]})
  # считывание текущего состояния сервера
  i_state = 'unknown'
  for item in vm_item.instance_view.statuses:
    if 'ProvisioningState/failed'.lower() in item.code.lower():
      i_state = 'failed'
      break
    if 'PowerState'.lower() in item.code.lower():
      i_state = item.display_status.replace('VM ','')
  # список доступа к управлению питанием
  i_start_stop_access = ''
  if 'Start-Stop access' in tags:
    i_start_stop_access = tags['Start-Stop access'].lower()
  # составление данных о бэкапе
  if vm_properties['backup_description'] != '':
    i_backup = 'Take a snapshot ' + vm_properties['backup_description']
  else:
    i_backup = ''
  if vm_properties['backup_latest'] != '':
    i_backup_latest = vm_properties['backup_latest']
    i_backup_latest = int(re_findall(r'\d+', i_backup_latest)[0])/1000 + 3600 # convert string, from milliseconds to seconds, from UTC to CET
    i_backup_latest = datetime.fromtimestamp(i_backup_latest).strftime("%d.%m.%Y %H:%M") + ' CET'
  else:
    i_backup_latest = ''
  # составление данных о времени работы
  if vm_properties['schedule_description'] != '':
    i_schedule = 'Running ' + vm_properties['schedule_description']
  else:
    i_schedule = ''
  # составление словаря с данными о сервера
  curValues = {
    'id': i_id,
    'name': vm_properties['name'],
    'type': vm_properties['type'],
    'description': vm_properties['description'],
    'zone': vm_properties['vnt_description'],
    'ip_address': vm_properties['ip_address'],
    'responsible': vm_properties['responsible'],
    'project': vm_properties['project'],
    'purpose': vm_properties['purpose'],
    'rgroup': vm_properties['rgroup'],
    'state': i_state,
    'backup': i_backup,
    'backup_latest': i_backup_latest,
    'schedule': i_schedule,
    'start_stop_access': i_start_stop_access,
  }
  # возврат словаря с данными о сервере
  return curValues


# запуск виртуальной машины
def azu_instance_start(vm_name):
  # получение облачных данных
  credentials, subscription_id = get_credentials()
  compute_client = ComputeManagementClient(credentials, subscription_id)
  vm_collection = compute_client.virtual_machines.list_all()
  # поиск нужной виртуальной машины
  for item in vm_collection:
    if item.name.lower() == vm_name.lower():
      vm_rgp = "".join(item.id.split('/')[4])
      break
  # запуск виртуальной машины
  result = compute_client.virtual_machines._start_initial(vm_rgp, vm_name)
  return result


# остановка виртуальной машины
def azu_instance_stop(vm_name):
  # получение облачных данных
  credentials, subscription_id = get_credentials()
  compute_client = ComputeManagementClient(credentials, subscription_id)
  vm_collection = compute_client.virtual_machines.list_all()
  # поиск нужной виртуальной машины
  for item in vm_collection:
    if item.name.lower() == vm_name.lower():
      vm_rgp = "".join(item.id.split('/')[4])
      break
  # остановка виртуальной машины 
  result = compute_client.virtual_machines._deallocate_initial(vm_rgp, vm_name)
  return result


# получение метрик сервера
def azu_instance_metrics(vm_name):
  import pandas as pd
  # составление временного интервала, за который нужно получить данные
  time_start = datetime.utcnow() - timedelta(hours=8)
  time_stop = datetime.utcnow()
  # получение локальных данных из файла
  vm_properties = azu_instance_properties_local(vm_name)
  # получение облачных данных
  credentials, subscription_id = get_credentials()
  monitor_client = MonitorManagementClient(credentials, subscription_id)
  # получение списка всех возможных метрик сервера !!! не используется, закомментировано
  #for metric in monitor_client.metric_definitions.list(vm_properties['path']):
  #  print("{}: id={}, unit={}".format(
  #      metric.name.localized_value,
  #      metric.name.value,
  #      metric.unit
  #  ))
  # получение метрики процессора
  metrics_data = monitor_client.metrics.list(
    resource_uri=vm_properties['path'],
    timespan="{}/{}".format(time_start, time_stop),
    interval='PT5M',
    metricnames='Percentage CPU',
    aggregation='Total'
  )
  # конвертация данных в формат googlecharts
  result = []
  for item in metrics_data.value:
    print("{} ({})".format(item.name.localized_value, item.unit))
    for timeserie in item.timeseries:
      for data in timeserie.data:
        if data.total == None:
          result.append([(data.time_stamp + timedelta(hours=1)).strftime('%H:%M'), 0, 0])
        else:
          result.append([(data.time_stamp + timedelta(hours=1)).strftime('%H:%M'), data.total, 0])
  return result


# функция сохранения меток для виртуальной машины
def azu_instance_save(vm_name, startstopaccess):
  credentials, subscription_id = get_credentials()
  compute_client = ComputeManagementClient(credentials, subscription_id)
  vm_collection = compute_client.virtual_machines.list_all()
  for item in vm_collection:
    if item.name.lower() == vm_name.lower():
      vm_rgp = "".join(item.id.split('/')[4])
      break
  vm_item = compute_client.virtual_machines.get(vm_rgp, vm_name)
  print(vm_item.tags)
  vm_item.tags = {
    'Start-Stop access': startstopaccess,
  }
  result = compute_client.virtual_machines._update_initial(
    vm_rgp,
    vm_name,
    vm_item,
  )
  return result

 
if __name__ == '__main__':
  i_item = azu_instance_save('wsAAA001', 'Python-test')
  print(i_item)
  #for i_keys, i_values in i_item.items():
  #  print(f'{i_keys}={i_values}')

 

Данный модуль я использую в проекте облегченного веб-сервиса для просмотра и управления облачными серверами, к которому имеют доступ многие сотрудники компании. Эти сотрудники не имеют доступа непосредственно к порталу Azure, но облегченная версия портала позволяет им выполнять нужные функции с серверами, сверяя доступ к серверам в группах Active Directory и в тегах Start-Stop access каждого сервера.