Поделиться через


Создание универсальных образов без агента подготовки

Область применения: ✔️ Виртуальные машины Linux ✔️ Гибкие масштабируемые наборы

Microsoft Azure предоставляет агенты подготовки для виртуальных машин Linux в виде walinuxagent или cloud-init (рекомендуется). Однако возможны сценарии, когда вам не подойдет использование для агента подготовки ни одного из этих приложений, например если:

  • Дистрибутив и версия Linux не поддерживает агент cloud-init/Linux.
  • Вам нужно задать определенные свойства виртуальной машины, например имя узла.

Примечание.

Если вам не нужно задавать какие-либо свойства или выполнять подготовку, рассмотрите возможность создания специализированного образа.

В этой статье показано, как настроить образ виртуальной машины для удовлетворения требований платформы Azure и задать имя узла без установки агента подготовки.

Подготовка к работе в сети и составлению отчетов

Чтобы виртуальная машина Linux взаимодействовала с компонентами Azure, требуется DHCP-клиент. Клиент используется для получения IP-адреса узла, разрешения DNS и управления маршрутизацией из виртуальной сети. Большинство дистрибутивов изначально поставляются с этими служебными программами. Средства, которые тестируются в Azure поставщиками дистрибутивов Linux, включают dhclientnetwork-managerи systemd-networkd другие.

Примечание.

В настоящее время создание универсальных образов без агента подготовки поддерживают только виртуальные машины с поддержкой DHCP.

После настройки и настройки сети выберите "Отчет готов". Это сообщает Azure, что виртуальная машина успешно подготовлена.

Внимание

Если сообщить о готовности Azure не удастся, виртуальная машина перезагрузится.

Демонстрационная версия и пример

Существующий образ Marketplace (в этом случае виртуальная машина Debian Buster) с агентом Linux (walinuxagent) удалена, и добавлен пользовательский скрипт Python — самый простой способ сообщить Azure, что виртуальная машина готова.

Создание группы ресурсов и базовой виртуальной машины:

$ az group create --location eastus --name demo1

Создание базовой виртуальной машины:

$ az vm create \
    --resource-group demo1 \
    --name demo1 \
    --location eastus \
    --ssh-key-value <ssh_pub_key_path> \
    --public-ip-address-dns-name demo1 \
    --image "debian:debian-10:10:latest"

Удаление агента подготовки образов

После подготовки виртуальной машины вы можете подключиться к ней через SSH и удалить агент Linux:

$ sudo apt purge -y waagent
$ sudo rm -rf /var/lib/waagent /etc/waagent.conf /var/log/waagent.log

Добавление требуемого кода в виртуальную машину

Кроме того, поскольку мы удалили агент Linux для Azure, виртуальной машине необходимо предоставить механизм для составления отчетов о готовности.

Скрипт Python

import http.client
import sys
from xml.etree import ElementTree

wireserver_ip = '168.63.129.16'
wireserver_conn = http.client.HTTPConnection(wireserver_ip)

print('Retrieving goal state from the Wireserver')
wireserver_conn.request(
    'GET',
    '/machine?comp=goalstate',
    headers={'x-ms-version': '2012-11-30'}
)

resp = wireserver_conn.getresponse()

if resp.status != 200:
    print('Unable to connect with wireserver')
    sys.exit(1)

wireserver_goalstate = resp.read().decode('utf-8')

xml_el = ElementTree.fromstring(wireserver_goalstate)

container_id = xml_el.findtext('Container/ContainerId')
instance_id = xml_el.findtext('Container/RoleInstanceList/RoleInstance/InstanceId')
incarnation = xml_el.findtext('Incarnation')
print(f'ContainerId: {container_id}')
print(f'InstanceId: {instance_id}')
print(f'Incarnation: {incarnation}')

# Construct the XML response we need to send to Wireserver to report ready.
health = ElementTree.Element('Health')
goalstate_incarnation = ElementTree.SubElement(health, 'GoalStateIncarnation')
goalstate_incarnation.text = incarnation
container = ElementTree.SubElement(health, 'Container')
container_id_el = ElementTree.SubElement(container, 'ContainerId')
container_id_el.text = container_id
role_instance_list = ElementTree.SubElement(container, 'RoleInstanceList')
role = ElementTree.SubElement(role_instance_list, 'Role')
instance_id_el = ElementTree.SubElement(role, 'InstanceId')
instance_id_el.text = instance_id
health_second = ElementTree.SubElement(role, 'Health')
state = ElementTree.SubElement(health_second, 'State')
state.text = 'Ready'

out_xml = ElementTree.tostring(
    health,
    encoding='unicode',
    method='xml'
)
print('Sending the following data to Wireserver:')
print(out_xml)

wireserver_conn.request(
    'POST',
    '/machine?comp=health',
    headers={
        'x-ms-version': '2012-11-30',
        'Content-Type': 'text/xml;charset=utf-8',
        'x-ms-agent-name': 'custom-provisioning'
    },
    body=out_xml
)

resp = wireserver_conn.getresponse()
print(f'Response: {resp.status} {resp.reason}')

wireserver_conn.close()

Сценарий Bash

#!/bin/bash

attempts=1
until [ "$attempts" -gt 5 ]
do
    echo "obtaining goal state - attempt $attempts"
    goalstate=$(curl --fail -v -X 'GET' -H "x-ms-agent-name: azure-vm-register" \
                                        -H "Content-Type: text/xml;charset=utf-8" \
                                        -H "x-ms-version: 2012-11-30" \
                                           "http://168.63.129.16/machine/?comp=goalstate")
    if [ $? -eq 0 ]
    then
       echo "successfully retrieved goal state"
       retrieved_goal_state=true
       break
    fi
    sleep 5
    attempts=$((attempts+1))
done

if [ "$retrieved_goal_state" != "true" ]
then
    echo "failed to obtain goal state - cannot register this VM"
    exit 1
fi

container_id=$(grep ContainerId <<< "$goalstate" | sed 's/\s*<\/*ContainerId>//g' | sed 's/\r$//')
instance_id=$(grep InstanceId <<< "$goalstate" | sed 's/\s*<\/*InstanceId>//g' | sed 's/\r$//')

ready_doc=$(cat << EOF
<?xml version="1.0" encoding="utf-8"?>
<Health xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <GoalStateIncarnation>1</GoalStateIncarnation>
  <Container>
    <ContainerId>$container_id</ContainerId>
    <RoleInstanceList>
      <Role>
        <InstanceId>$instance_id</InstanceId>
        <Health>
          <State>Ready</State>
        </Health>
      </Role>
    </RoleInstanceList>
  </Container>
</Health>
EOF
)

attempts=1
until [ "$attempts" -gt 5 ]
do
    echo "registering with Azure - attempt $attempts"
    curl --fail -v -X 'POST' -H "x-ms-agent-name: azure-vm-register" \
                             -H "Content-Type: text/xml;charset=utf-8" \
                             -H "x-ms-version: 2012-11-30" \
                             -d "$ready_doc" \
                             "http://168.63.129.16/machine?comp=health"
    if [ $? -eq 0 ]
    then
       echo "successfully register with Azure"
       break
    fi
    sleep 5 # sleep to prevent throttling from wire server
done

Универсальные шаги (если не используются Python или Bash)

Если для виртуальной машины не установлен или не доступен язык Python, вы можете программным образом воспроизвести приведенную выше логику скрипта, выполнив следующие действия:

  1. Извлеките ContainerId, InstanceId и Incarnation, проанализировав отклик от WireServer: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Создайте следующие XML-данные, подставив данные ContainerId, InstanceId и Incarnation, полученные с помощью синтаксического анализа на предыдущем этапе.

    <Health>
      <GoalStateIncarnation>INCARNATION</GoalStateIncarnation>
      <Container>
        <ContainerId>CONTAINER_ID</ContainerId>
        <RoleInstanceList>
          <Role>
            <InstanceId>INSTANCE_ID</InstanceId>
            <Health>
              <State>Ready</State>
            </Health>
          </Role>
        </RoleInstanceList>
      </Container>
    </Health>
    
  3. Опубликуйте эти данные в WireServer: curl -X POST -H 'x-ms-version: 2012-11-30' -H "x-ms-agent-name: WALinuxAgent" -H "Content-Type: text/xml;charset=utf-8" -d "$REPORT_READY_XML" http://168.63.129.16/machine?comp=health

Автоматизация выполнения кода при первой загрузке

В этой демонстрационной версии используется systemd, служба которая является наиболее распространенной системой инициализации в современных дистрибутивах Linux. Таким образом, самый простой и эффективный способ гарантировать, что этот механизм создания отчетов о готовности будет выполняться в нужное время, — это создать единицу службы systemd. Вы можете добавить следующий файл единицы в /etc/systemd/system (в этом примере файл единицы будет называться azure-provisioning.service):

[Unit]
Description=Azure Provisioning

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /usr/local/azure-provisioning.py
ExecStart=/bin/bash -c "hostnamectl set-hostname $(curl \
    -H 'metadata: true' \
    'http://169.254.169.254/metadata/instance/compute/name?api-version=2019-06-01&format=text')"
ExecStart=/usr/bin/systemctl disable azure-provisioning.service

[Install]
WantedBy=multi-user.target

Эта служба systemd выполняет для базовой подготовки три действия:

  1. Создает отчет о готовности к работе для Azure (чтобы сообщить об успешной подготовке).
  2. Переименовывает виртуальную машину на основе имени предоставленной пользователем виртуальной машины, извлекая эти данные из Службы метаданных экземпляров Azure (IMDS). Примечание. IMDS также предоставляет другие метаданные экземпляров, такие как открытые ключи SSH, поэтому вы можете задать больше параметров, чем просто имя узла.
  3. Автоматически отключается, и таким образом гарантирует, что служба будет выполнятся при первой загрузке и отключаться при последующих перезагрузках.

Создав единицу в файловой системе, выполните следующие действия, чтобы включить службу:

$ sudo systemctl enable azure-provisioning.service

Теперь виртуальная машина обобщена и на ее основе создан образ.

Завершение подготовки образа

Выполните на компьютере разработки следующие действия, чтобы подготовиться к созданию образа из базовой виртуальной машины:

$ az vm deallocate --resource-group demo1 --name demo1
$ az vm generalize --resource-group demo1 --name demo1

Создайте образ на основе этой виртуальной машины:

$ az image create \
    --resource-group demo1 \
    --source demo1 \
    --location eastus \
    --name demo1img

Теперь мы готовы создать виртуальную машину на основе образа. Это также можно использовать для создания нескольких виртуальных машин:

$ IMAGE_ID=$(az image show -g demo1 -n demo1img --query id -o tsv)
$ az vm create \
    --resource-group demo12 \
    --name demo12 \
    --location eastus \
    --ssh-key-value <ssh_pub_key_path> \
    --public-ip-address-dns-name demo12 \
    --image "$IMAGE_ID"
    --enable-agent false

Примечание.

Важно задать для --enable-agent значение false, потому что на этой виртуальной машине, которая будет создана из образа, walinuxagent нет.

Виртуальная машина должна быть подготовлена успешно. После входа в только что подготовленную виртуальную машину вы сможете просмотреть выходные данные готовой службы отчетов:

$ sudo journalctl -u azure-provisioning.service
-- Logs begin at Thu 2020-06-11 20:28:45 UTC, end at Thu 2020-06-11 20:31:24 UTC. --
Jun 11 20:28:49 thstringnopa systemd[1]: Starting Azure Provisioning...
Jun 11 20:28:54 thstringnopa python3[320]: Retrieving goal state from the Wireserver
Jun 11 20:28:54 thstringnopa python3[320]: ContainerId: 7b324f53-983a-43bc-b919-1775d6077608
Jun 11 20:28:54 thstringnopa python3[320]: InstanceId: fbb84507-46cd-4f4e-bd78-a2edaa9d059b._thstringnopa2
Jun 11 20:28:54 thstringnopa python3[320]: Sending the following data to Wireserver:
Jun 11 20:28:54 thstringnopa python3[320]: <Health><GoalStateIncarnation>1</GoalStateIncarnation><Container><ContainerId>7b324f53-983a-43bc-b919-1775d6077608</ContainerId><RoleInstanceList><Role><InstanceId>fbb84507-46cd-4f4e-bd78-a2edaa9d059b._thstringnopa2</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>
Jun 11 20:28:54 thstringnopa python3[320]: Response: 200 OK
Jun 11 20:28:56 thstringnopa bash[472]:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Jun 11 20:28:56 thstringnopa bash[472]:                                  Dload  Upload   Total   Spent    Left  Speed
Jun 11 20:28:56 thstringnopa bash[472]: [158B blob data]
Jun 11 20:28:56 thstringnopa2 systemctl[475]: Removed /etc/systemd/system/multi-user.target.wants/azure-provisioning.service.
Jun 11 20:28:56 thstringnopa2 systemd[1]: azure-provisioning.service: Succeeded.
Jun 11 20:28:56 thstringnopa2 systemd[1]: Started Azure Provisioning.

Поддержка

Если вы реализуете собственный код или агент подготовки, значит вы должны предоставлять поддержку для этого кода. В этом случае служба поддержки Майкрософт будет исследовать только проблемы, связанные с недоступностью интерфейсов подготовки. Мы постоянно делаем улучшения и изменения в этой области, поэтому необходимо отслеживать изменения в cloud-init и агенте Linux Azure для подготовки изменений API.

Следующие шаги

Подробнее см. в статье о подготовке Linux.