Creación de imágenes generalizadas sin un agente de aprovisionamiento
Se aplica a: ✔️ máquinas virtuales Linux ✔️ conjuntos de escalado flexibles
Microsoft Azure proporciona agentes de aprovisionamiento para máquinas virtuales Linux en forma de walinuxagent o cloud-init (recomendado). Sin embargo, podría haber un escenario en el que no quiera usar ninguna de estas aplicaciones para el agente de aprovisionamiento, como:
- Su distribución o versión de Linux no admite el Agente Linux/cloud-init.
- Necesita que se establezcan propiedades de máquina virtual específicas, como el nombre de host.
Nota
Si no necesita que se establezca ninguna propiedad ni que se produzca ningún tipo de aprovisionamiento, debería considerar la posibilidad de crear una imagen especializada.
En este artículo se muestra cómo puede configurar la imagen de la máquina virtual para cumplir los requisitos de la plataforma de Azure y establecer el nombre de host, sin instalar un agente de aprovisionamiento.
Redes e informes listos
Para que la máquina virtual Linux se comunique con los componentes de Azure, se requiere un cliente DHCP. El cliente se usa para recuperar una dirección IP de host, la resolución DNS y la administración de rutas de la red virtual. La mayoría de las distribuciones se envían con estas utilidades integradas. Entre las herramientas que han probado los proveedores de distribución de Linux en Azure se incluyen dhclient
, network-manager
, systemd-networkd
y otras.
Nota
Actualmente, la creación de imágenes generalizadas sin un agente de aprovisionamiento solo admite máquinas virtuales habilitadas para DHCP.
Una vez que se hayan configurado las redes, seleccione "informar que está listo". Esto indica a Azure que la máquina virtual se ha aprovisionó correctamente.
Importante
Si no informa a Azure de que está listo, se reiniciará la máquina virtual.
Demostración/ejemplo
Una imagen de Marketplace existente (en este caso, una máquina virtual de Debian Buster) con el agente Linux (walinuxagent) quitado y un script de Python personalizado agregado es la manera más fácil de indicar a Azure que la máquina virtual está "lista".
Cree el grupo de recursos y la máquina virtual base:
$ az group create --location eastus --name demo1
Cree la máquina virtual base:
$ 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"
Eliminación del agente de aprovisionamiento de imágenes
Una vez que se aprovisiona la máquina virtual, puede conectarla mediante SSH en ella y quitar el Agente Linux:
$ sudo apt purge -y waagent
$ sudo rm -rf /var/lib/waagent /etc/waagent.conf /var/log/waagent.log
Incorporación del código necesario a la máquina virtual
También dentro de la máquina virtual, dado que hemos quitado el Agente Linux de Azure, debemos proporcionar un mecanismo para informar de que estamos listos.
Script de 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()
Script de 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
Pasos genéricos (si no se usa Python o Bash)
Si la máquina virtual no tiene Python instalado ni tampoco disponible, puede reproducir esta lógica de script anterior mediante programación con los pasos siguientes:
Recupere los valores
ContainerId
,InstanceId
yIncarnation
mediante el análisis de la respuesta de WireServer:curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate
.Inserte los valores
ContainerId
,InstanceId
yIncarnation
analizados en el paso anterior y construya los siguientes datos XML:<Health> <GoalStateIncarnation>INCARNATION</GoalStateIncarnation> <Container> <ContainerId>CONTAINER_ID</ContainerId> <RoleInstanceList> <Role> <InstanceId>INSTANCE_ID</InstanceId> <Health> <State>Ready</State> </Health> </Role> </RoleInstanceList> </Container> </Health>
Publique estos datos en 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
Automatización de la ejecución del código en el primer arranque
En esta demostración se usa systemd, que es el sistema de inicialización más habitual en las distribuciones de Linux modernas. Así pues, la forma más sencilla y nativa de garantizar que este mecanismo para informar de que se está listo se ejecute en el momento adecuado es crear una unidad de servicio de systemd. Puede agregar el siguiente archivo de unidad a /etc/systemd/system
(en este ejemplo, el archivo de unidad se denomina 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
Este servicio de systemd realiza tres acciones para el aprovisionamiento básico:
- Informa a Azure de que está listo (para indicar que apareció correctamente).
- Cambia el nombre de la máquina virtual en función del nombre de la máquina virtual proporcionado por el usuario extrayendo estos datos de Azure Instance Metadata Service (IMDS). Tenga en cuenta que IMDS también proporciona otros metadatos de instancia, como las claves públicas SSH, por lo que puede establecer más de un nombre de host.
- Se deshabilita por su cuenta, de modo que solo se ejecute en el primer arranque y no en reinicios posteriores.
Con la unidad en el sistema de archivos, ejecute lo siguiente para su habilitación:
$ sudo systemctl enable azure-provisioning.service
Ahora la máquina virtual está lista para generalizarse y tener una imagen creada a partir de ella.
Finalización de la preparación de la imagen
De nuevo en la máquina de desarrollo, ejecute lo siguiente a fin de prepararse para la creación de imágenes a partir de la máquina virtual base:
$ az vm deallocate --resource-group demo1 --name demo1
$ az vm generalize --resource-group demo1 --name demo1
Asimismo, cree la imagen a partir de esta máquina virtual:
$ az image create \
--resource-group demo1 \
--source demo1 \
--location eastus \
--name demo1img
Ahora estamos listos para crear una nueva máquina virtual a partir de la imagen. También se puede usar para crear varias máquinas virtuales:
$ 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
Nota
Es importante establecer --enable-agent
en false
, ya que walinuxagent no existe en esta máquina virtual que se va a crear a partir de la imagen.
La máquina virtual se debe aprovisionar correctamente. Después de iniciar sesión en la máquina virtual recién aprovisionada, debería poder ver la salida del servicio systemd listo para el informe:
$ 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.
Soporte técnico
Si implementa su propio código o agente de aprovisionamiento, contará con el soporte técnico de este código. El soporte técnico de Microsoft solo investigará aquellas incidencias relacionadas con las interfaces de aprovisionamiento no disponibles. Continuamente realizamos mejoras y cambios en esta área, por lo que debe supervisar los cambios en el Agente Linux de Azure y cloud-init para aprovisionar los cambios de API.
Pasos siguientes
Para más información, consulte Aprovisionamiento de Linux.