vApp options - OVF environment

Existe um método que permite a configuração de parâmetros específicos de um sistema operacional através da passagem de seus valores ao realizar o deploy de um OVF. Isso é feito por meio do recurso vApp disponibilizado pela VMware, parcialmente (logo mais o motivo ficará explícito). Existem outras facilidades dele, mas nessa postagem somente é mostrado como preparar uma estratégia para que o SUSE Linux Enterprise Server 11 SP3 seja autoconfigurado no âmbito de estrutura básica de rede como IP, máscara, gateway, DNS primário, DNS secundário, NTP, hostname e domínio.

O primeiro passo é habilitar o vApp options na edição da VM. No tree view dessa opção surgirá mais 4 itens: Properties, IP Allocation Policy, OVF Settings e Advanced.


Na opção Advanced clique no botão IP Allocation e marque a opção OVF Environment.


Os parâmetros para o usuário digitar ao realizar o deploy são configurados na janela mostrada abaixo ao clicar no botão Properties.


Veja um exemplo da configuração de um campo do tipo IP.


Na opção OVF Settings marque a opção VMware Tools. Isso permite que obtemos os valores configurados a partir de um XML no contexto de execução do sistema operacional pelo comando vmtoolsd --cmd "info-get guestinfo.ovfenv" no shell.


Veja que o botão View fica bloqueado. Ele só é liberado ao ligar a VM.

Por fim na opção Properties podemos visualizar como será mostrado os campos no deploy do OVF.


Agora se clicarmos no botão View depois de a VM ter sido iniciada ele pode mostrar, por exemplo, o seguinte XML:

<?xml version="1.0" encoding="UTF-8"?>
<Environment
xmlns="http://schemas.dmtf.org/ovf/environment/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oe="http://schemas.dmtf.org/ovf/environment/1"
xmlns:ve="http://www.vmware.com/schema/ovfenv"
oe:id=""
ve:vCenterId="vm-610">
<PlatformSection>
<Kind>VMware ESXi</Kind>
<Version>5.1.0</Version>
<Vendor>VMware, Inc.</Vendor>
<Locale>en</Locale>
</PlatformSection>
<PropertySection>
<Property oe:key="dns1" oe:value="8.8.8.8"/>
<Property oe:key="dns2" oe:value="8.8.4.4"/>
<Property oe:key="domain" oe:value="yoursite.com"/>
<Property oe:key="gateway" oe:value="10.222.21.254"/>
<Property oe:key="hostname" oe:value="minidlsss"/>
<Property oe:key="ip" oe:value="10.222.21.69"/>
<Property oe:key="netmask" oe:value="24"/>
<Property oe:key="ntp" oe:value="0.br.pool.ntp.org"/>
</PropertySection>
<ve:EthernetAdapterSection>
<ve:Adapter ve:mac="00:50:56:92:35:01" ve:network="VM Network SSC" ve:unitNumber="7"/>
</ve:EthernetAdapterSection>
</Environment>

É justamente ele que será disponibilizado pela VMware Tools.

Um ponto importante é que podemos testar a configuração em função desses valores até funcionar para distribuir o OVF. Para fazer isso eu desenvolvi o seguinte script em python (a leitura do XML eu achei na postagem do Martin Amdisen):

#! /usr/bin/python
# Script to set network parameters from 'vmtoolsd --cmd "info-get guestinfo.ovfenv"'. It is designed for SLES 11 SP3
# Author: Willian Antunes
# Date of creation: September 03, 2014
import os, sys, time, subprocess, re
from xml.dom.minidom import parseString
def findXmlSection(dom, sectionName):
sections = dom.getElementsByTagName(sectionName)
return sections[0]
def getPropertyMap(ovfEnv):
dom = parseString(ovfEnv)
section = findXmlSection(dom, "PropertySection")
propertyMap = {}
for property in section.getElementsByTagName("Property"):
key = property.getAttribute("oe:key")
value = property.getAttribute("oe:value")
propertyMap[key] = value
dom.unlink()
return propertyMap
print "Issuing the command echo `vmtoolsd --cmd \"info-get guestinfo.ovfenv\"` to get the vApp configuration..."
ovfEnv = subprocess.Popen("echo `vmtoolsd --cmd \"info-get guestinfo.ovfenv\"`", shell=True, stdout=subprocess.PIPE).stdout.read()
propertyMap = getPropertyMap(ovfEnv)
ip = propertyMap["ip"]
netmask = propertyMap["netmask"]
gateway = propertyMap["gateway"]
dns1 = propertyMap["dns1"]
dns2 = propertyMap["dns2"]
hostname = propertyMap["hostname"]
domain = propertyMap["domain"]
ntp = propertyMap["ntp"]
print "The following properties will be used to set network parameters: "
print "--- IP: ", ip
print "--- Netmask: ", netmask
print "--- Gateway: ", gateway
print "--- Preferred DNS server: ", dns1
print "--- Alternate DNS server: ", dns2
print "--- Hostname: ", hostname
print "--- domain: ", domain
print "--- ntp: ", ntp
print "Cleaning up the device list..."
# It will prevent the case when the VM has two NICs. Generally it occurs when a new deploy is made
print subprocess.Popen("yast2 lan delete id=0 verbose", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("yast2 lan delete id=1 verbose", shell=True, stdout=subprocess.PIPE).stdout.read()
print "Obtaining device name..."
p = subprocess.Popen('yast2 lan list', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
returnCode = p.wait()
stdout = p.stdout.read()
deviceName = re.search(r'\s.*,', stdout).group().strip()[:-1]
print "Device name: ", deviceName
print "Configuring NTP parameters..."
print subprocess.Popen("yast2 ntp-client add server=%s" % (ntp), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("yast2 ntp-client enable", shell=True, stdout=subprocess.PIPE).stdout.read()
print "Configuring IP parameters..."
print subprocess.Popen("echo \"default %s - -\" > /etc/sysconfig/network/routes" % (gateway), shell=True, stdout=subprocess.PIPE).stdout.read()
print "Creating the file ifcfg-eth0..."
print subprocess.Popen("echo \"BOOTPROTO='static'\" > /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"BROADCAST=''\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"ETHTOOL_OPTIONS=''\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"IPADDR='%s/%s'\" >> /etc/sysconfig/network/ifcfg-eth0" % (ip, netmask), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"MTU=''\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"NAME='%s'\" >> /etc/sysconfig/network/ifcfg-eth0" % (deviceName), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"NETWORK=''\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"REMOTE_IPADDR=''\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"STARTMODE='auto'\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"USERCONTROL='no'\" >> /etc/sysconfig/network/ifcfg-eth0", shell=True, stdout=subprocess.PIPE).stdout.read()
print "File created!"
print "Setting the current NIC to use 'eth0' as its name..."
print subprocess.Popen("head -n -1 /etc/udev/rules.d/70-persistent-net.rules | cat > /tmp/70-persistent-net.rules", shell=True, stdout=subprocess.PIPE).stdout.read()
netRules = subprocess.Popen("tail -n 1 /etc/udev/rules.d/70-persistent-net.rules", shell=True, stdout=subprocess.PIPE).stdout.read()
newNameValue = ", NAME=\"eth0\""
netRulesNew = netRules[:-(len(netRules)-netRules.rindex(','))] + newNameValue
# The command bellow will allow python to pass double quotes to the shell
netRulesNew = netRulesNew.replace("\"", "\\\"")
print subprocess.Popen("echo \"{0}\" >> /tmp/70-persistent-net.rules".format(netRulesNew), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("mv /tmp/70-persistent-net.rules /etc/udev/rules.d/70-persistent-net.rules", shell=True, stdout=subprocess.PIPE).stdout.read()
print "Configuring DNS parameters..."
print subprocess.Popen("sed -i \"s/NETCONFIG_DNS_STATIC_SERVERS=.*/NETCONFIG_DNS_STATIC_SERVERS=\\\"%s %s\\\"/g\" /etc/sysconfig/network/config" % (dns1, dns2), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("head -n -1 /etc/hosts | cat > /tmp/hosts", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"%s %s.%s %s\" >> /tmp/hosts" % (ip, hostname, domain, hostname), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("mv /tmp/hosts /etc/hosts", shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"{0}.{1}\" > /etc/HOSTNAME".format(hostname, domain), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"nameserver {0}\" >> /etc/resolv.conf".format(dns1), shell=True, stdout=subprocess.PIPE).stdout.read()
print subprocess.Popen("echo \"nameserver {0}\" >> /etc/resolv.conf".format(dns2), shell=True, stdout=subprocess.PIPE).stdout.read()
print "Restarting the network service..."
print subprocess.Popen("/etc/init.d/network restart", shell=True, stdout=subprocess.PIPE).stdout.read()
print "The configuration has been applied"
Caso seja feita a execução dele os valores já serão obtidos e configurados no SLES.

Depois dos testes é necessário preparar a VM para exportar um OVF. Existem alguns pré-requisitos como não ter entradas no /etc/resolv.conf e na tabela de consultas do NTP, mas o principal é garantir que o script seja executado uma única vez no SLES. No meu caso criei um serviço para ser iniciado no runlevel 3 (depois de network) que chama o script em python, exclui ele depois da configuração, desfaz o registro para ele próprio não ser mais executado como serviço para, no final, se auto excluir não deixando rastros no SLES, mas é claro, isso é só uma sugestão.

Para maiores detalhes sugiro os seguintes links:

Comentários