Freigeben über


Erstellen generalisierter Images ohne Bereitstellungs-Agent

Gilt für: ✔️ Linux-VMs ✔️ Flexible Skalierungsgruppen

Microsoft Azure bietet Bereitstellungs-Agents für Linux-VMs in Form von walinuxagent oder cloud-init (empfohlen). Es kann jedoch Situationen geben, in denen Sie keine dieser Anwendungen für Ihren Bereitstellungs-Agent verwenden möchten, wie z. B:

  • Die Linux-Distribution/-Version unterstützt cloud-init bzw. den Linux-Agent nicht.
  • Es müssen bestimmte VM-Eigenschaften festgelegt werden, z. B. Hostname.

Hinweis

Wenn Sie keine Eigenschaften festlegen müssen oder keine Form von Bereitstellung benötigen, sollten Sie die Erstellung eines speziellen Images in Betracht ziehen.

In diesem Artikel wird gezeigt, wie Sie Ihr VM-Image so einrichten können, dass es die Anforderungen der Azure-Plattform erfüllt, und den Hostnamen festlegen, ohne einen Bereitstellungs-Agent zu installieren.

Netzwerkeinrichtung und Melden des Status „Bereit“

Damit Ihre Linux-VM mit Azure-Komponenten kommunizieren kann, ist ein DHCP-Client erforderlich. Der Client wird verwendet, um eine Host-IP-Adresse, DNS-Auflösung und Routenverwaltung aus dem virtuellen Netzwerk abzurufen. Die meisten Distributionen bieten diese Hilfsprogramme standardmäßig. Zu den Tools, die von Linux-Distributionsanbietern mit Azure getestet wurden, gehören u. a. dhclient, network-manager und systemd-networkd.

Hinweis

Derzeit wird das Erstellen generalisierter Images ohne Bereitstellungs-Agent nur von DHCP-fähigen VMs unterstützt.

Nachdem das Netzwerk eingerichtet und konfiguriert wurde, wählen Sie den Status „Bereit“ aus. Dadurch wird Azure mitgeteilt, dass die VM erfolgreich bereitgestellt wurde.

Wichtig

Wenn Azure nicht der Status „Bereit“ gemeldet wird, erfolgt ein Neustart des virtuellen Computers.

Demo/Beispiel

Ein vorhandenes Marketplace-Image (in diesem Fall eine Debian Buster-VM) mit entferntem Linux-Agent (walinuxagent) und einem hinzugefügten benutzerdefinierten Python-Skript ist die einfachste Möglichkeit, Azure mitzuteilen, dass die VM „bereit“ ist.

Erstellen Sie die Ressourcengruppe und Basis-VM:

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

Erstellen Sie die Basis-VM:

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

Entfernen des Bereitstellung-Agents aus dem Image

Nach Bereitstellung der VM können Sie sich über SSH mit ihr verbinden und den Linux-Agent entfernen:

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

Hinzufügen des erforderlichen Codes zur VM

Innerhalb der VM müssen wir auch, da wir den Azure-Linux-Agent entfernt haben, einen Mechanismus bereitstellen, mit dem wir „Bereit“ melden können.

Python-Skript

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

#!/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

Generische Schritte (wenn Python oder Bash nicht verwendet werden)

Wenn Python nicht in Ihrer VM installiert oder verfügbar ist, können Sie die obige Skriptlogik anhand der folgenden Schritte programmgesteuert reproduzieren:

  1. Rufen Sie ContainerId, InstanceId und Incarnation durch Analysieren der Antwort von WireServer ab: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Erstellen Sie die folgenden XML-Daten, indem Sie die analysierten Werte für ContainerId, InstanceId und Incarnation aus dem obigen Schritt einfügen:

    <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. Senden Sie diese Daten an 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

Automatisieren der Ausführung des Codes beim ersten Start

In dieser Demo wird systemd verwendet, das häufigste Initialisierungssystem moderner Linux-Distributionen. Die einfachste und nativste Möglichkeit sicherzustellen, dass dieser Mechanismus zum Melden von „Bereit“ zum richtigen Zeitpunkt ausgeführt wird, ist also die Erstellung einer Diensteinheit für systemd. Sie können /etc/systemd/system die folgende Einheitsdatei hinzufügen (in diesem Beispiel wird die Einheitsdatei azure-provisioning.service genannt):

[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

Dieser Dienst systemd führt zur grundlegenden Bereitstellung drei Aufgaben aus:

  1. Meldet Azure den Status „Bereit“ (um auf die erfolgreiche Einrichtung hinzuweisen)
  2. Benennt die VM basierend auf dem vom Benutzer angegebenen VM-Namen um, indem diese Daten aus Azure Instance Metadata Service (IMDS) abgerufen werden Hinweis: IMDS stellt auch andere Instanzmetadaten bereit, z. B. öffentliche SSH-Schlüssel, daher können Sie mehr als den Hostnamen festlegen.
  3. Deaktiviert sich selbst, sodass dieser Dienst nur beim ersten Start und nicht bei nachfolgenden Neustarts ausgeführt wird

Wenn sich die Einheit im Dateisystem befindet, führen Sie folgenden Befehl aus, um sie zu aktivieren:

$ sudo systemctl enable azure-provisioning.service

Nun kann die VM generalisiert und ein Image von ihr erstellt werden.

Abschließen der Vorbereitung des Images

Führen Sie auf Ihrem Entwicklungscomputer die folgenden Schritte aus, um das Erstellen des Images anhand der Basis-VM vorzubereiten:

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

Erstellen Sie das Image anhand dieser VM:

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

Jetzt sind wir bereit, eine neue VM anhand des Images zu erstellen. Dies kann auch verwendet werden, um mehrere VMs zu erstellen:

$ 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

Hinweis

Es ist wichtig, --enable-agent auf false festzulegen, da walinuxagent auf dieser VM, die anhand des Images erstellt werden soll, nicht vorhanden ist.

Die VM sollte erfolgreich bereitgestellt werden. Nach dem Anmelden bei der neu bereitgestellten VM sollte die Ausgabe des Diensts systemd den Status „Bereit“ melden:

$ 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

Wenn Sie Ihren eigenen Bereitstellungscode/-Agent implementieren, sind Sie für den Support dieses Codes verantwortlich. Der Microsoft-Support untersucht lediglich Probleme im Zusammenhang mit der Nichtverfügbarkeit der Bereitstellungsschnittstellen. Wir arbeiten ständig an Verbesserungen und Änderungen in diesem Bereich. Daher müssen Sie auf Änderungen bei cloud-init und an der Bereitstellungs-API des Azure-Linux-Agents achten.

Nächste Schritte

Weitere Informationen finden Sie unter Bereitstellung von Linux.