Creazione di immagini generalizzate senza un agente di provisioning

Si applica a: ✔️ macchine virtuali Linux ✔️ set di scalabilità flessibili

Microsoft Azure fornisce agenti di provisioning per macchine virtuali Linux sotto forma di walinuxagent o cloud-init (scelta consigliata). Tuttavia, potrebbe esserci uno scenario in cui non si vuole usare una di queste applicazioni per l'agente di provisioning, ad esempio:

  • La distribuzione/versione di Linux non supporta l'agente cloud-init/Linux.
  • È necessario impostare proprietà specifiche della macchina virtuale, ad esempio il nome host.

Nota

Se non è necessario impostare proprietà o qualsiasi forma di provisioning, è consigliabile creare un'immagine specializzata.

Questo articolo illustra come configurare l'immagine della macchina virtuale per soddisfare i requisiti della piattaforma Azure e impostare il nome host, senza installare un agente di provisioning.

Rete e segnalazione di una macchina come pronta

Per fare in modo che la macchina virtuale Linux comunichi con i componenti di Azure, è necessario un client DHCP. Il client viene usato per recuperare un indirizzo IP host, una risoluzione DNS e una gestione delle route dalla rete virtuale. La maggior parte delle distribuzioni è disponibile con queste utilità predefinite. Gli strumenti testati in Azure dai fornitori di distribuzioni Linux includono dhclient, network-manager, systemd-networkd e altri.

Nota

Attualmente la creazione di immagini generalizzate senza un agente di provisioning supporta solo macchine virtuali abilitate per DHCP.

Dopo aver configurato e configurato la rete, selezionare "Segnala come pronta". Questo indica ad Azure che il provisioning della macchina virtuale è stato eseguito correttamente.

Importante

Se non si riesce a segnalare la preparazione ad Azure, la macchina virtuale verrà riavviata.

Demo/esempio

Un'immagine del Marketplace esistente (in questo caso, una macchina virtuale Debian Buster) con l'agente Linux (walinuxagent) rimossa e uno script Python personalizzato aggiunto è il modo più semplice per indicare ad Azure che la macchina virtuale è "pronta".

Creare il gruppo di risorse e la macchina virtuale di base:

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

Creare la macchina virtuale di 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"

Rimuovere l'agente di provisioning delle immagini

Dopo aver effettuato il provisioning della macchina virtuale, è possibile connettersi tramite SSH e rimuovere l'agente Linux:

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

Aggiungere il codice necessario alla macchina virtuale

Anche all'interno della macchina virtuale, poiché è stato rimosso l'agente Linux di Azure, è necessario fornire un meccanismo per segnalare che la macchina è pronta.

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

Passaggi generici (se non si usano Python o Bash)

Se la macchina virtuale non ha Python installato o disponibile, è possibile riprodurre a livello di codice questa logica di script precedente seguendo questa procedura:

  1. Recuperare ContainerId, InstanceId e Incarnation analizzando la risposta da WireServer: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Costruire i dati XML seguenti, inserendo i valori ContainerId, InstanceId e Incarnation analizzati dal passaggio precedente:

    <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. Pubblicare questi dati in 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

Automazione dell'esecuzione del codice al primo avvio

Questa demo usa systemd, che è il sistema init più comune nelle distribuzioni Linux moderne. Pertanto, il modo più semplice e nativo per garantire che questo meccanismo di segnalazione della prontezza venga eseguito al momento giusto consiste nel creare un'unità di servizio di sistema. È possibile aggiungere il file di unità seguente a /etc/systemd/system (in questo esempio il file di 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

Questo servizio systemd esegue tre operazioni per il provisioning di base:

  1. Segnalazioni di prontezza ad Azure (per indicare la corretta esecuzione).
  2. Rinomina la macchina virtuale in base al nome della macchina virtuale fornita dall'utente eseguendo il pull di questi dati dal Servizio metadati dell'istanza di Azure (IMDS). Nota IMDS fornisce anche altri metadati dell'istanza, ad esempio chiavi pubbliche SSH, in modo da poter impostare altri elementi oltre al nome host.
  3. Si disabilita in modo che venga eseguito solo al primo avvio e non ai riavvii successivi.

Con l'unità nel file system, eseguire quanto segue per abilitarla:

$ sudo systemctl enable azure-provisioning.service

Ora la macchina virtuale è pronta per essere generalizzata e creare un'immagine.

Completamento della preparazione dell'immagine

Tornare al computer di sviluppo per preparare la creazione di immagini dalla macchina virtuale di base:

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

Creare l'immagine da questa macchina virtuale:

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

A questo punto è possibile creare una nuova macchina virtuale dall'immagine. Questa operazione può essere usata anche per creare più macchine virtuali:

$ 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

È importante impostare --enable-agent su false perché walinuxagent non esiste in questa macchina virtuale che verrà creata dall'immagine.

Il provisioning della macchina virtuale deve essere eseguito correttamente. Dopo aver eseguito l'accesso alla macchina virtuale appena sottoposta a provisioning, dovrebbe essere possibile visualizzare l'output del servizio di sistema di segnalazione della prontezza:

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

Supporto tecnico

Se si implementa un codice/agente di provisioning personalizzato, si è proprietari del supporto di questo codice. Il supporto tecnico Microsoft esaminerà solamente i problemi relativi alle interfacce di provisioning non disponibili. Stiamo apportando continuamente miglioramenti e modifiche in questa area, quindi è necessario monitorare le modifiche apportate a cloud-init e all'agente Linux di Azure per il provisioning delle modifiche dell'API.

Passaggi successivi

Per altre informazioni, vedere Provisioning Linux.