Partager via


Création d’images généralisées sans agent d’approvisionnement

S’applique à : ✔️ Machines virtuelles Linux ✔️ Groupes identiques flexibles

Microsoft Azure fournit des agents d’approvisionnement pour les machines virtuelles Linux sous la forme de walinuxagent ou cloud-init (recommandé). Toutefois, il peut y avoir un scénario où vous ne souhaitez pas utiliser l’une de ces applications pour votre agent d’approvisionnement, par exemple :

  • Votre version/distribution Linux ne prend pas en charge cloud-init/l’agent Linux.
  • Vous devez définir des propriétés de machine virtuelle spécifiques, telles que le nom d'hôte.

Notes

Si vous n’avez pas besoin de définir des propriétés ou une forme d’approvisionnement, vous devez envisager de créer une image spécialisée.

Cet article décrit comment configurer l’image de votre machine virtuelle pour répondre aux exigences de la plateforme Azure et définir le nom d’hôte, sans installer d’agent d’approvisionnement.

Préparer la mise en réseau et les rapports

Pour que votre machine virtuelle Linux communique avec les composants Azure, un client DHCP est nécessaire. Le client est utilisé pour récupérer une adresse IP hôte, une résolution DNS et la gestion des itinéraires à partir du réseau virtuel. La plupart des distributions sont livrées avec ces utilitaires prêts à l’emploi. Les outils qui sont testés sur Azure par les fournisseurs de distribution Linux incluent dhclient, network-manager, systemd-networkd et d’autres.

Notes

Actuellement, la création d’images généralisées sans agent d’approvisionnement ne prend en charge que les machines virtuelles compatibles DHCP.

Après avoir paramétrer et configurer la mise en réseau, sélectionnez « rapport prêt ». Cela indique à Azure que la machine virtuelle a été approvisionnée avec succès.

Important

Si vous ne réussissez pas à créer de rapports sur Azure, votre machine virtuelle sera redémarrée.

Démonstration/Exemple

Une image de la place de marché existante (dans ce cas, une machine virtuelle Debian Buster) sur laquelle l’agent Linux (walinuxagent) est supprimé et un script Python personnalisé est ajouté est le moyen le plus simple de dire à Azure que la machine virtuelle est « prête ».

Créez le groupe de ressources et la machine virtuelle de base :

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

Créez la machine virtuelle de 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"

Supprimer l’image de l’agent d’approvisionnement

Une fois la machine virtuelle approvisionnée, vous pouvez vous y connecter via SSH et supprimer l’agent Linux :

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

Ajouter le code requis à la machine virtuelle

Étant donné que nous avons supprimé l’agent Linux Azure, nous devons fournir un mécanisme de rapport prêt à l’intérieur de la machine virtuelle.

Script 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 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

Étapes génériques (si vous n’utilisez pas Python ou Bash)

Si Python n’est pas installé ou disponible sur votre machine virtuelle, vous pouvez reproduire par programmation la logique de script ci-dessus en procédant comme suit :

  1. Récupérez les ContainerId, InstanceId et Incarnation en analysant la réponse de WireServer : curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Construisez les données XML suivantes, en injectant ContainerId analysé, InstanceId et Incarnation à partir de l’étape ci-dessus :

    <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. Publiez ces données dans 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

Automatisation de l’exécution du code au premier démarrage

Cette démonstration utilise SystemD, qui est le système init le plus courant dans les distributions Linux modernes. La méthode la plus simple et la plus native pour s’assurer que ce mécanisme prêt pour les rapports s’exécute au bon moment est la création d’une unité de service système. Vous pouvez ajouter le fichier d’unité suivant à /etc/systemd/system (cet exemple nomme le fichier d’unité 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

Ce service système fait trois choses pour l’approvisionnement de base :

  1. Préparez les rapports pour Azure (pour indiquer qu’ils ont été correctement exécutés).
  2. Renomme la machine virtuelle en fonction du nom de machine virtuelle fourni par l’utilisateur en extrayant ces données à partir d’Azure Instance Metadata Service (IMDS). Remarque IMDS fournit également d’autres métadonnées d’instance, telles que des clés publiques SSH, pour vous permettre de définir davantage le nom d’hôte.
  3. Se désactive pour s’exécuter uniquement au premier démarrage et non pas lors des redémarrages suivants.

Avec l’unité sur le système de fichiers, exécutez la commande suivante pour l’activer :

$ sudo systemctl enable azure-provisioning.service

La machine virtuelle est maintenant prête à être généralisée et une image est créée à partir de celle-ci.

Fin de la préparation de l’image

De retour sur votre ordinateur de développement, exécutez la commande suivante pour préparer la création d’images à partir de la machine virtuelle de base :

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

Et créez l’image à partir de cette machine virtuelle :

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

Nous sommes maintenant prêts à créer une machine virtuelle à partir de l’image. Cela peut également être utilisé pour créer plusieurs machines virtuelles :

$ 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

Notes

Il est important de définir --enable-agent sur false, car walinuxagent n’existe pas sur la machine virtuelle qui va être créée à partir de l’image.

La machine virtuelle doit être approvisionnée correctement. Après vous être connecté à la machine virtuelle qui vient d’être approvisionnée, vous devez pouvoir consulter la sortie du service système « rapport prêt » :

$ 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.

Support

Si vous implémentez votre propre code/agent de provisionnement, vous possédez la prise en charge de ce code, le support technique de Microsoft n’examinera que les problèmes liés aux interfaces d’approvisionnement non disponibles. Nous apportons continuellement des améliorations et des modifications dans ce domaine. Par conséquent, vous devez surveiller les modifications apportées à cloud-init et à l’agent Linux Azure concernant l’approvisionnement des modifications de l’API.

Étapes suivantes

Pour plus d’informations, consultez Provisionnement Linux.