Tworzenie uogólnionych obrazów bez agenta aprowizacji

Dotyczy: ✔️ Maszyny wirtualne z systemem Linux — elastyczne zestawy skalowania ✔️

Platforma Microsoft Azure udostępnia agentów aprowizacji dla maszyn wirtualnych z systemem Linux w postaci walinuxagent lub cloud-init (zalecane). Jednak może wystąpić scenariusz, w którym nie chcesz używać żadnej z tych aplikacji dla agenta aprowizacji, na przykład:

  • Dystrybucja/wersja systemu Linux nie obsługuje agenta cloud-init/Linux.
  • Należy ustawić określone właściwości maszyny wirtualnej, takie jak nazwa hosta.

Uwaga

Jeśli nie musisz ustawiać żadnych właściwości ani żadnej formy aprowizacji, należy rozważyć utworzenie wyspecjalizowanego obrazu.

W tym artykule przedstawiono sposób konfigurowania obrazu maszyny wirtualnej w celu spełnienia wymagań platformy Azure i ustawienia nazwy hosta bez instalowania agenta aprowizacji.

Gotowe do obsługi sieci i raportowania

Aby maszyna wirtualna z systemem Linux komunikowała się ze składnikami platformy Azure, wymagany jest klient DHCP. Klient służy do pobierania adresu IP hosta, rozpoznawania nazw DNS i zarządzania trasami z sieci wirtualnej. Większość dystrybucji dostarcza te narzędzia gotowe do użycia. Narzędzia testowane na platformie Azure przez dostawców dystrybucji systemu Linux obejmują dhclient, network-managersystemd-networkd i inne.

Uwaga

Obecnie tworzenie uogólnionych obrazów bez agenta aprowizacji obsługuje tylko maszyny wirtualne z obsługą protokołu DHCP.

Po skonfigurowaniu i skonfigurowaniu sieci wybierz pozycję "Raport gotowy". Informuje to platformę Azure, że maszyna wirtualna została pomyślnie aprowizowana.

Ważne

Nie można zgłosić gotowości na platformę Azure, co spowoduje ponowne uruchomienie maszyny wirtualnej.

Pokaz/przykład

Istniejący obraz witryny Marketplace (w tym przypadku maszyna wirtualna Debian Buster) z agentem systemu Linux (walinuxagent) został usunięty, a dodany niestandardowy skrypt języka Python jest najprostszym sposobem na powiedzenie platformie Azure, że maszyna wirtualna jest "gotowa".

Utwórz grupę zasobów i podstawową maszynę wirtualną:

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

Utwórz podstawową maszynę wirtualną:

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

Usuwanie agenta aprowizacji obrazów

Po aprowizacji maszyny wirtualnej możesz nawiązać z nią połączenie za pośrednictwem protokołu SSH i usunąć agenta systemu Linux:

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

Dodawanie wymaganego kodu do maszyny wirtualnej

Również wewnątrz maszyny wirtualnej, ponieważ usunęliśmy agenta systemu Linux platformy Azure, musimy udostępnić mechanizm gotowy do raportowania.

Skrypt języka 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()

Skrypt powłoki 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

Ogólne kroki (jeśli nie używasz języka Python lub powłoki Bash)

Jeśli maszyna wirtualna nie ma zainstalowanego ani dostępnego języka Python, możesz programowo odtworzyć tę powyżej logikę skryptu, wykonując następujące kroki:

  1. Pobierz element ContainerId, InstanceIdi Incarnation , analizujejąc odpowiedź z serwera WireServer: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Skonstruuj następujące dane XML, iniekcyjne przeanalizowane ContainerId, InstanceIdi Incarnation z powyższego kroku:

    <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. Opublikuj te dane na 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

Automatyzowanie uruchamiania kodu podczas pierwszego rozruchu

W tym pokazie używany jest systemd, który jest najczęściej używanym systemem init w nowoczesnych dystrybucjach systemu Linux. Najprostszym i najbardziej natywnym sposobem zapewnienia, że ten mechanizm gotowy do raportowania działa w odpowiednim czasie, jest utworzenie systemowej jednostki usługi. Do pliku jednostki można dodać następujący plik /etc/systemd/system jednostkowy (w tym przykładzie nazwa pliku jednostki 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

Ta systemowa usługa wykonuje trzy czynności na potrzeby podstawowej aprowizacji:

  1. Raporty gotowe na platformę Azure (aby wskazać, że zostały wykonane pomyślnie).
  2. Zmienia nazwę maszyny wirtualnej na podstawie nazwy maszyny wirtualnej dostarczonej przez użytkownika, pobierając te dane z usługi Azure Instance Metadata Service (IMDS). Uwaga Usługa IMDS udostępnia również inne metadane wystąpienia, takie jak klucze publiczne SSH, dzięki czemu można ustawić więcej niż nazwa hosta.
  3. Wyłącza się tak, aby był uruchamiany tylko podczas pierwszego rozruchu, a nie podczas kolejnych ponownych rozruchów.

W ramach lekcji w systemie plików uruchom następujące polecenie, aby ją włączyć:

$ sudo systemctl enable azure-provisioning.service

Teraz maszyna wirtualna jest gotowa do uogólninia i ma utworzony na jej podstawie obraz.

Kończenie przygotowywania obrazu

Po powrocie do maszyny programistycznej uruchom następujące polecenie, aby przygotować się do utworzenia obrazu z podstawowej maszyny wirtualnej:

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

Utwórz obraz na podstawie tej maszyny wirtualnej:

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

Teraz możemy utworzyć nową maszynę wirtualną na podstawie obrazu. Można to również użyć do utworzenia wielu maszyn wirtualnych:

$ 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

Uwaga

Ważne jest, aby ustawić wartość na --enable-agentfalse , ponieważ program walinuxagent nie istnieje na tej maszynie wirtualnej, która zostanie utworzona na podstawie obrazu.

Maszyna wirtualna powinna zostać pomyślnie aprowizowana. Po zalogowaniu się do nowo aprowizowanej maszyny wirtualnej powinny być widoczne dane wyjściowe usługi gotowej do obsługi raportu:

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

Pomoc techniczna

Jeśli zaimplementujesz własny kod/agent aprowizacji, będziesz właścicielem tego kodu, pomoc techniczna firmy Microsoft zbada tylko problemy związane z niedostępnymi interfejsami aprowizacji. Nieustannie wprowadzamy ulepszenia i zmiany w tym obszarze, dlatego musisz monitorować zmiany w programie cloud-init i azure Linux Agent w celu aprowizacji zmian interfejsu API.

Następne kroki

Aby uzyskać więcej informacji, zobacz Aprowizowanie systemu Linux.