Создание универсальных образов без агента подготовки
Область применения: ✔️ Виртуальные машины Linux ✔️ Гибкие масштабируемые наборы
Microsoft Azure предоставляет агенты подготовки для виртуальных машин Linux в виде walinuxagent или cloud-init (рекомендуется). Однако возможны сценарии, когда вам не подойдет использование для агента подготовки ни одного из этих приложений, например если:
- Дистрибутив и версия Linux не поддерживает агент cloud-init/Linux.
- Вам нужно задать определенные свойства виртуальной машины, например имя узла.
Примечание
Если вам не нужно задавать какие-либо свойства или выполнять подготовку, рассмотрите возможность создания специализированного образа.
В этой статье показано, как настроить образ виртуальной машины для удовлетворения требований платформы Azure и задать имя узла без установки агента подготовки.
Чтобы виртуальная машина Linux взаимодействовала с компонентами Azure, требуется DHCP-клиент. Клиент используется для получения IP-адреса узла, разрешения DNS и управления маршрутизацией из виртуальной сети. Большинство дистрибутивов изначально поставляются с этими служебными программами. Средства, которые тестируются в Azure поставщиками дистрибутивов Linux, включают dhclient
network-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, виртуальной машине необходимо предоставить механизм для составления отчетов о готовности.
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()
#!/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, вы можете программным образом воспроизвести приведенную выше логику скрипта, выполнив следующие действия:
Извлеките
ContainerId
,InstanceId
иIncarnation
, проанализировав отклик от WireServer:curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate
.Создайте следующие 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>
Опубликуйте эти данные в 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 выполняет для базовой подготовки три действия:
- Создает отчет о готовности к работе для Azure (чтобы сообщить об успешной подготовке).
- Переименовывает виртуальную машину на основе имени предоставленной пользователем виртуальной машины, извлекая эти данные из Службы метаданных экземпляров Azure (IMDS). Примечание. IMDS также предоставляет другие метаданные экземпляров, такие как открытые ключи SSH, поэтому вы можете задать больше параметров, чем просто имя узла.
- Автоматически отключается, и таким образом гарантирует, что служба будет выполнятся при первой загрузке и отключаться при последующих перезагрузках.
Создав единицу в файловой системе, выполните следующие действия, чтобы включить службу:
$ 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.