프로비저닝 에이전트 없이 일반화된 이미지 만들기

적용 대상: ✔️ Linux VM ✔️ 유연한 확장 집합

Microsoft Azure는 Linux VM에 대한 프로비저닝 에이전트를 walinuxagent 또는 cloud-init(권장) 형식으로 제공합니다. 그러나 다음과 같은 프로비저닝 에이전트에 이러한 애플리케이션 중 하나를 사용하지 않으려는 시나리오가 있을 수 있습니다.

  • Linux 배포판/버전은 cloud-init/Linux 에이전트를 지원하지 않습니다.
  • 호스트 이름과 같은 특수화된 VM 속성을 설정해야 합니다.

참고 항목

속성을 설정할 필요가 없거나 어떤 형태의 프로비저닝도 필요하지 않은 경우 특수 이미지를 만드는 것을 고려해야 합니다.

이 문서에서는 프로비전 에이전트를 설치하지 않고도 Azure 플랫폼 요구 사항을 충족하고 호스트 이름을 설정하도록 VM 이미지를 설정하는 방법을 보여 줍니다.

네트워킹 및 보고 준비 완료

Linux VM이 Azure 구성 요소와 통신하도록 하려면 DHCP 클라이언트가 필요합니다. 클라이언트는 가상 네트워크에서 호스트 IP, DNS 확인, 경로 관리를 검색하는 데 사용됩니다. 대부분의 배포판은 이러한 유틸리티를 기본 제공합니다. Linux 배포판 공급업체가 Azure에서 테스트하는 도구에는 dhclient, network-manager, systemd-networkd 등이 있습니다.

참고 항목

현재 프로비저닝 에이전트 없이 일반화된 이미지 만들기는 DHCP 사용 VM만 지원합니다.

네트워킹을 설정하고 구성한 후 “보고 준비”를 선택합니다. 그러면 Azure에 VM이 성공적으로 프로비전 중임을 알 수 있습니다.

Important

Azure에 준비를 보고하지 못하면 VM이 다시 부팅됩니다.

데모/샘플

Linux 에이전트(walinuxagent)가 제거되고 사용자 지정 Python 스크립트가 추가된 기존 Marketplace 이미지(이 경우 Debian Buster VM)는 VM이 “준비”되었음을 Azure에 알리는 가장 쉬운 방법입니다.

리소스 그룹 및 기본 VM 만들기:

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

기본 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"

이미지 프로비저닝 에이전트 제거

VM이 프로비전되면 SSH를 통해 VM에 연결하고 Linux 에이전트를 제거할 수 있습니다.

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

VM에 필요한 코드 추가

또한 VM 내부에서 Azure Linux 에이전트를 제거했으므로 준비를 보고할 수 있는 메커니즘을 제공해야 합니다.

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()

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

일반 단계(Python 또는 Bash를 사용하지 않는 경우)

VM에 Python이 설치되어 있지 않거나 사용할 수 없는 경우 다음 단계를 사용하 여 위의 스크립트 논리를 프로그래밍 방식으로 재현할 수 있습니다.

  1. WireServer: curl -X GET -H 'x-ms-version: 2012-11-30' http://168.63.129.16/machine?comp=goalstate에서 응답을 구문 분석하여 ContainerId, InstanceIdIncarnation을 검색합니다.

  2. 위 단계에서 구문 분석된 ContainerId, InstanceIdIncarnation을 삽입하여 다음 XML 데이터를 생성합니다.

    <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. 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에 이 데이터 게시

처음 부팅할 때 코드 실행 자동화

이 데모는 최신 Linux 배포판에서 가장 일반적인 init 시스템인 systemd를 사용합니다. 따라서 이 보고 준비 메커니즘이 적시에 실행되도록 하는 가장 쉽고 기본적인 방법은 systemd 서비스 단위를 만드는 것입니다. 다음 단위 파일을 /etc/systemd/system에 추가할 수 있습니다(이 예제는 단위 파일 이름을 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

이 systemd 서비스는 기본 프로비저닝을 위한 세 가지 작업을 수행합니다.

  1. Azure에 준비를 보고합니다(성공적으로 완료되었음을 나타냄).
  2. Azure IMDS(Azure Instance Metadata Service)에서 이 데이터를 가져와 사용자가 제공한 VM 이름에 따라 VM 이름을 변경합니다. 참고 IMDS는 또한 SSH 공개 키와 같은 다른 인스턴스 메타데이터를 제공하므로 호스트 이름보다 더 많은 항목을 설정할 수 있습니다.
  3. 처음 부팅할 때만 실행되며 이후에 재부팅할 때는 실행되지 않도록 자체 비활성화합니다.

파일 시스템의 단위를 사용해 다음을 실행하여 활성화합니다.

$ sudo systemctl enable azure-provisioning.service

이제 VM을 일반화할 준비가 되었고, VM에서 이미지를 만들 수 있습니다.

이미지 준비 완료

개발 컴퓨터로 돌아가서 다음을 실행하여 기본 VM에서 이미지를 만들 준비를 합니다.

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

그리고 이 VM에서 이미지를 만듭니다.

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

이제 이미지에서 새 VM을 만들 준비가 되었습니다. 여러 VM을 만드는 데에도 사용할 수 있습니다.

$ 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

참고 항목

이미지에서 생성될 이 VM에 walinuxagent이 존재하지 않기 때문에 --enable-agent(을)를 false(으)로 설정하는 것이 중요합니다.

VM을 성공적으로 프로비전해야 합니다. 새로 프로비전된 VM에 로그인한 후 보고 준비가 완료된 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.

지원

자체 프로비저닝 코드/에이전트를 구현하고 이 코드에 대한 지원을 소유한 경우 Microsoft 지원에서는 사용할 수 없는 프로비저닝 인터페이스와 관련된 문제만 조사합니다. 이 영역에서 지속적으로 개선 및 변경 작업을 수행하고 있으므로 API 변경 사항을 프로비전하기 위해 cloud-init 및 Azure Linux 에이전트에서 변경 사항을 모니터링해야 합니다.

다음 단계

자세한 내용은 Linux 프로비저닝을 참조하세요.