Criar imagens generalizadas sem um agente de provisionamento

Aplica-se a: ✔️ VMs do Linux ✔️ Conjuntos de dimensionamento flexíveis

O Microsoft Azure fornece agentes de provisionamento para VMs Linux na forma do walinuxagent ou cloud-init (recomendado). Mas pode haver um cenário em que você não queira usar nenhum desses aplicativos para o agente de provisionamento, como:

  • Sua versão/distribuição do Linux não dá suporte ao agente cloud-init/Linux.
  • Você precisa que propriedades específicas da VM sejam definidas, como nome do host.

Observação

Se você não precisar que nenhuma propriedade seja definida ou que qualquer forma de provisionamento aconteça, considere a criação de uma imagem especializada.

Este artigo mostra como você pode configurar sua imagem de VM para atender aos requisitos da plataforma Azure e definir o nome do host, sem instalar um agente de provisionamento.

Rede e relatórios prontos

Para que sua VM Linux se comunique com componentes do Azure, um cliente DHCP é necessário. O cliente é usado para recuperar um IP do host, resolução DNS e gerenciamento de rotas da rede virtual. A maioria dos distribuições é fornecida com esses utilitários prontos para uso. As ferramentas que foram testadas no Azure por fornecedores de distribuição do Linux incluem dhclient, network-manager, systemd-networkd e outros.

Observação

Atualmente, a criação de imagens generalizadas sem um agente de provisionamento dá suporte apenas a VMs habilitadas para DHCP.

Depois que a rede tiver sido definida e configurada, você deverá "relatar pronto". Isso dirá ao Azure que a VM foi provisionada com êxito.

Importante

A falha ao relatar pronto para o Azure resultará na reinicialização da VM!

Demonstração/exemplo

Uma imagem existente do Marketplace (nesse caso, uma VM Debian Buster) com o Agente do Linux (walinuxagent) removido e um script python personalizado adicionado é a maneira mais fácil de informar ao Azure que a VM está "pronta".

Criar o grupo de recursos e a VM base:

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

Criar a VM 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"

Remover o agente de provisionamento de imagem

Depois que a VM estiver provisionando, você poderá usar o SSH nela e remover o agente do Linux:

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

Adicionar o código necessário à VM

Além disso, dentro da VM, como removemos o agente Linux do Azure, precisamos fornecer um mecanismo para relatar prontamente.

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

Etapas genéricas (se não estiver usando o Python nem o Bash)

Se sua VM não tiver o Python instalado ou disponível, você poderá reproduzir programaticamente a lógica de script acima com as seguintes etapas:

  1. Recupere ContainerId, InstanceId e Incarnation analisando a resposta do WireServer: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate.

  2. Construa os seguintes dados XML, injetando o analisado ContainerId, InstanceId e Incarnation a partir da etapa acima:

    <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. Publique esses dados no 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

Automatizar a execução do código na primeira inicialização

Esta demonstração usa o systemd, que é o sistema de inicialização mais comum em distribuições modernas do Linux. Portanto, a maneira mais fácil e nativa de garantir que esse mecanismo de relatório pronto seja executado no momento certo é criar uma unidade de serviço do systemd. Você pode adicionar o seguinte arquivo de unidade a /etc/systemd/system (este exemplo nomeia o arquivo de unidade 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

Esse serviço do systemd faz três coisas para o provisionamento básico:

  1. Relata como pronto para o Azure (para indicar que ele foi fornecido com êxito).
  2. Renomeia a VM com base no nome da VM fornecida pelo usuário ao extrair esses dados do Serviço de Metadados de Instância do Azure (IMDS). Observação O IMDS também fornece outros metadados de instância, como chaves públicas SSH, para que você possa definir mais do que o nome do host.
  3. Desabilita a si mesmo para só ser executado na primeira inicialização e não nas reinicializações subsequentes.

Com a unidade no sistema de arquivos, execute o seguinte para habilitá-lo:

$ sudo systemctl enable azure-provisioning.service

Agora a VM está pronta para ser generalizada e tem uma imagem criada a partir dela.

Concluir a preparação da imagem

De volta ao seu computador de desenvolvimento, execute o seguinte para preparar a criação da imagem da VM de base:

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

E crie a imagem a partir desta VM:

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

Agora, estamos prontos para criar uma nova VM (ou várias VMs) a partir da imagem. Isso também pode ser usado para criar várias VMs:

$ 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

Observação

É importante definir --enable-agent como false porque o walinuxagent não existe nessa VM que será criada com base na imagem.

A VM deve ser provisionada com êxito. Após fazer logon na nova VM de provisionamento, você deve ser capaz de ver a saída do relatório pronto serviço de systemd:

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

Suporte

Se você implementar seu próprio código/agente de provisionamento, terá o suporte desse código. O suporte da Microsoft investigará apenas os problemas relacionados às interfaces de provisionamento que não estão disponíveis. Estamos continuamente fazendo melhorias e alterações nessa área. Portanto, você deve monitorar as alterações na cloud-init e no Agente Linux do Azure para provisionar alterações de API.

Próximas etapas

Para obter mais informações, consulte Provisionamento do Linux.