Configuração de Máquinas Virtuais Libvirt via Script Shell

Definição da Topologia de Rede

O script automatiza a criação de redes virtuais utiliazndo libvirt, com base em uma configuração modular. A estrutura principal é definida por variáveis que podem ser ajsutadas conforme necessário:

# Estrutura: nome_da_rede: bridge_name,método_forward,cidr_inicial
# forward pode ser: nat, route ou isolated
REDES=(
    'ansible_edge: asedgebr0,nat'
    'smartcity_edge: scedgebr0,route'
    'smartcity_cloud: sccloudbr0,nat'
    'ansible_cloud: ascloudbr0,route'
)

# Mapeamento de interfaces por host e índice de interface
declare -A MAC_INTERFACES
MAC_INTERFACES=(
    [ansible_edge]='EDGE1:1 EDGE2:1 EDGE3:1 HUB:1'
    [smartcity_edge]='EDGE1:2 EDGE2:2 EDGE3:2 HUB:2'
    [smartcity_cloud]='CLOUD:2 HUB:3'
    [ansible_cloud]='CLOUD:1'
)

IP_BASE=192.168.124.2
MODO_PADRAO=route

# Armazena listas de endereços MAC gerados para cada host
declare -A LISTA_MACS=([EDGE1]="" [EDGE2]="" [EDGE3]="" [HUB]="" [CLOUD]="")

Ferramentas de Análise da Estrutura de Rede

Funções auxiliares extraem informações das strings de configuração:

function analisar_rede() {
    local entrada="$1" chave="$2"
    IFS=':, ' read -ra partes <<< "$entrada"
    partes[2]=${partes[2]:-nat}
    
    declare -A campos=([nome]=0 [bridge]=1 [modo]=2 [cidr]=3)
    local indice=${campos[$chave]:-$chave}
    echo "${partes[$indice]}"
}

function obter_nome_rede() {
    echo "${1%:*}"
}

function buscar_config_rede() {
    local var_lista="$1" nome_rede="$2"
    for idx in $(eval echo "\${!$var_lista[@]}"); do
        local valor=$(eval echo "\${$var_lista[$idx]}")
        if [[ "${valor%:*}" == "$nome_rede" ]]; then
            echo "$valor"
            return
        fi
    done
}

function extrair_ip() {
    grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' <<< "$1" | head -n1
}

function obter_ip_rede() {
    local modo=$(analisar_rede "$1" modo)
    local candidato=$(extrair_ip "$modo")
    local ip_final=${candidato:-$(analisar_rede "$1" cidr)}
    extrair_ip "$ip_final"
}

function obter_modo_rede() {
    local modo=$(analisar_rede "$1" modo)
    local tem_ip=$(extrair_ip "$modo")
    [[ -n "$tem_ip" ]] && modo=$(analisar_rede "$1" cidr)
    echo "${modo:-nat}"
}

Geração de Endereços MAC Únicos

Utiliza Python para criar endereços MAC válidos no padrão reservado para virtualização (00:16:3e):

function gerar_mac() {
    python3 <<EOF
import random
def novo_mac():
    octetos = [0x00, 0x16, 0x3e,
               random.randint(0x00, 0x7f),
               random.randint(0x00, 0xff),
               random.randint(0x00, 0xff)]
    return ':'.join(f"{byte:02x}" for byte in octetos)
print(novo_mac())
EOF
}

Criação do Pool de Endereços MAC

Gera múltiplos MACs para cada host e salva no ambiente:

for host in "${!LISTA_MACS[@]}"; do
    for i in {1..5}; do
        LISTA_MACS[$host]+="$(gerar_mac) "
    done
    echo "export ${host}_MACS=\"${LISTA_MACS[$host]}\"" >> ~/.bashrc
done

Manipulação de Interfaces de Rede

Recupera os MACs associados a uma rede específica:

function listar_macs_rede() {
    local mapa_mac="$1" nome_rede="$2"
    local indices=$(eval echo "\${$mapa_mac[$nome_rede]}")
    local resultado=()
    
    for item in $indices; do
        local host="${item%%:*}" idx="${item##*:}"
        local lista_var="${host}_MACS"
        local macs_brutos=$(eval echo "\${$lista_var}")
        local mac_array=($macs_brutos)
        resultado+=("${mac_array[idx]}")
    done
    
    echo "${resultado[@]}"
}

Geração de Nomes e IPs

Cria nomes de hosts padronizados e sequências de IP:

function gerar_lista_hosts() {
    local mapa_mac="$1" rede="$2"
    local indices=$(eval echo "\${$mapa_mac[$rede]}")
    echo "${indices//:/.}" | tr '[:upper:]' '[:lower:]'
}

function incrementar_subrede() {
    local subrede="${1%.*.*}.$((${1#*.*.}%.* + $2))).${1##*.}"
    echo "$subrede"
}

function gerar_ips() {
    local mapa_mac="$1" rede="$2" base_ip="$3"
    local hosts=($(gerar_lista_hosts "$mapa_mac" "$rede"))
    local ips=()
    for i in "${!hosts[@]}"; do
        ips+=("$(incrementar_subrede "$base_ip" "$i")")
    done
    echo "${ips[@]}"
}

Configuração DHCP

Gera entradas XML para alocação estática de IP via DHCP:

function elemento_dhcp() {
    local mac="$1" nome_host="$2" dominio="$3" ip="$4"
    local dom_real="${dominio/_/.}"
    echo "      <host mac=\"$mac\" name=\"${nome_host}.${dom_real}.com\" ip=\"$ip\"/>"
}

function gerar_blocos_dhcp() {
    local mapa_mac="$1" rede="$2" ip_inicial="$3"
    local hosts=($(gerar_lista_hosts "$mapa_mac" "$rede"))
    local macs=($(listar_macs_rede "$mapa_mac" "$rede"))
    local ips=($(gerar_ips "$mapa_mac" "$rede" "$ip_inicial"))
    
    local saida=()
    for i in "${!hosts[@]}"; do
        saida+=("$(elemento_dhcp "${macs[i]}" "${hosts[i]}" "$rede" "${ips[i]}")")
    done
    
    IFS=$'\n'; echo "${saida[*]}"
}

Geração de XML de Rede

Produz arquivos XML para definição de redes no libvirt:

function criar_xml_rede() {
    local nome_rede="$1" bridge="$2" ip_base="$3" dhcp="$4" modo="$5"
    local mascara="255.255.255.0"
    local gateway="${ip_base%.*}.1"
    local faixa_inicio="${ip_base%.*}.2"
    local faixa_fim="${ip_base%.*}.192"
    
    mkdir -p /tmp/vir_network
    cat > "/tmp/vir_network/$nome_rede.xml" << EOF
<network>
  <name>$nome_rede</name>
  <bridge name='$bridge'/>
  <forward mode='$modo'/>
  <ip address='$gateway' netmask='$mascara'>
    <dhcp>
      <range start='$faixa_inicio' end='$faixa_fim'/>
$dhcp
    </dhcp>
  </ip>
</network>
EOF
}

function gerar_todas_redes() {
    local var_lista="$1" var_mac="$2" ip_base="$3" modo_global="$4"
    ip_base=${ip_base:-192.168.124.2}
    modo_global=${modo_global:-$MODO_PADRAO}
    
    for indice in $(eval echo "\${!$var_lista[@]}"); do
        local config=$(eval echo "\${$var_lista[$indice]}")
        local nome_rede=$(obter_nome_rede "$config")
        local bridge=$(analisar_rede "$config" bridge)
        local ip_rede=$(incrementar_subrede "$ip_base" "$indice")
        local ip_definido=$(obter_ip_rede "$config")
        ip_rede=${ip_definido:-$ip_rede}
        
        local blocos_dhcp=$(gerar_blocos_dhcp "$var_mac" "$nome_rede" "$ip_rede")
        local modo_rede=$(obter_modo_rede "$config")
        criar_xml_rede "$nome_rede" "$bridge" "$ip_rede" "$blocos_dhcp" "${modo_global:-$modo_rede}"
    done
}

Operações de Rede

Comandos básicos para ativar e remover redes:

virsh net-create /tmp/vir_network/ansible_edge.xml
virsh net-list --all
virsh net-destroy ansible_edge

Configuração de Máquina Virtual

Define parâmetros iniciais para a VM:

NOME_VM=smartcity_cloud
MEMORIA_GB=10
CPUS=6
EMULADOR=/usr/libexec/qemu-kvm
DISCO_IMAGEM=/var/lib/libvirt/images/cloud.qcow2

Interface com Hardware Virtual

Recupera dados de interface de um host específico:

function obter_interfaces_host() {
    local mapa_mac="$1" hostname="$2"
    local maiusculo=${hostname^^}
    local saida=()
    
    for rede in $(eval echo "\${!$mapa_mac[@]}"); do
        local valor=$(eval echo "\${$mapa_mac[$rede]}")
        local correspondencia=$(grep -o "$maiusculo:[0-9]*" <<< "$valor")
        [[ $? -ne 0 ]] && continue
        
        local indice=${correspondencia#*:}
        local mac_var="${maiusculo}_MACS"
        local lista_mac=$(eval echo "\${$mac_var}")
        local array_mac=($lista_mac)
        local endereco_mac=${array_mac[indice]}
        
        saida+=("$maiusculo $indice $rede $endereco_mac,")
    done
    
    IFS=$'\n' sorted=($(sort <<<"${saida[*]}")); unset IFS
    echo "${sorted[@]}"
}

function obter_bridge_rede() {
    local var_lista="$1" nome="$2"
    for idx in $(eval echo "\${!$var_lista[@]}"); do
        local valor=$(eval echo "\${$var_lista[$idx]}")
        if [[ "${valor%:*}" == "$nome" ]]; then
            echo "$(analisar_rede "$valor" bridge)"
            return
        fi
    done
}

Geração de XML de Interface

Produz trechos XML para interfaces de rede da VM:

function criar_xml_interface() {
    local tipo_rede="$1" mac="$2" bridge="$3"
    local modelo=${MODELO_IFC:-rtl8139}
    
    cat << EOF
    <interface type='network'>
      <mac address='$mac'/>
      <source network='$tipo_rede' bridge='$bridge'/>
      <model type='$modelo'/>
    </interface>
EOF
}

function gerar_xml_interfaces() {
    local mapa_mac="$1" host="$2" map_rede="$3"
    local dados=($(obter_interfaces_host "$mapa_mac" "$host"))
    local saida=()
    
    IFS=',' read -ra itens <<< "${dados[*]}"
    for item in "${itens[@]}"; do
        [[ -z "$item" ]] && continue
        local partes=($item)
        local bridge=$(obter_bridge_rede "$map_rede" "${partes[2]}")
        local xml=$(criar_xml_interface "${partes[2]}" "${partes[3]}" "$bridge")
        saida+=("$xml")
    done
    
    IFS=$'\n'; echo "${saida[*]}"
}

Modelo Completo de VM

Gera o arquivo XML completo para definição da máquina virtual:

function gerar_xml_vm() {
    local mapa_mac="$1" nome_host="$2" map_rede="$3"
    
    [[ -z "$DISCO_ORIGEM" ]] && return 1
    [[ -z "$NOME_VM" ]] && return 1
    
    MEMORIA_GB=${MEMORIA_GB:-10}
    CPUS=${CPUS:-4}
    
    DISCO_IMAGEM=${DISCO_ORIGEM%/*}/$2.${DISCO_ORIGEM#*.}
    cp "$DISCO_ORIGEM" "$DISCO_IMAGEM"
    virt-edit -a "$DISCO_IMAGEM" /etc/hostname -e "s/.*/$2/"
    
    local script_ifcfg=$(gerar_script_interface)
    virt-edit -a "$DISCO_IMAGEM" /etc/sysconfig/network-scripts/ifcfg-ens192 -e 's/BOOTPROTO=.*/BOOTPROTO=dhcp/'
    
    mkdir -p /tmp/vir_domain
    local interfaces=$(gerar_xml_interfaces "$mapa_mac" "$nome_host" "$map_rede")
    
    cat > "/tmp/vir_domain/${NOME_VM}.xml" << EOF
<domain type='kvm'>
  <name>$NOME_VM</name>
  <memory unit='GiB'>$MEMORIA_GB</memory>
  <vcpu placement='static'>$CPUS</vcpu>
  <os>
    <type arch='x86_64' machine='pc-i440fx-rhel7.0.0'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/><apic/>
  </features>
  <devices>
    <emulator>$EMULADOR</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='$DISCO_IMAGEM'/>
      <target dev='vda' bus='virtio'/>
    </disk>
$interfaces
    <serial type='pty'><target port='0'/></serial>
    <console type='pty' target_type='serial'/>
  </devices>
</domain>
EOF
}

Inicialização da VM

Fluxo final para criar e iniciar a instância:

HOST=cloud
DISCO_ORIGEM=/var/lib/libvirt/images/base-centos7.qcow2
NOME_VM=smartcity_$HOST
MEMORIA_GB=10
CPUS=6

gerar_xml_vm NET_MACS $HOST NET_NAMES
virsh define /tmp/vir_domain/${NOME_VM}.xml
virsh start $NOME_VM
virsh list --all

Redimensionamento de Disco

Expande automaticamente partições LVM dentro da VM:

function redimensionar_lvm() {
    local alvo="$1" novo_tamanho="$2"
    local disco_img=$(virsh dumpxml "$alvo" | xmllint --xpath "//disk/source/@file" - 2>/dev/null | cut -d'"' -f2)
    
    local info_fs=$(virt-filesystems --long -h --all -a "$disco_img")
    local particao=$(echo "$info_fs" | grep pv | tail -n1 | awk '{print $1}')
    local volume_grupo=$(echo "$info_fs" | grep vg | tail -n1 | awk '{print $1}')
    local volume_logico=$(echo "$info_fs" | grep "$volume_grupo/root" | awk '{print $1}')
    local sistema_arq=$(echo "$info_fs" | grep filesystem | grep "$volume_logico" | awk '{print $3}')
    
    local comando_fs=$( [[ "$sistema_arq" == "xfs" ]] && echo "xfs_growfs" || echo "resize2fs" )
    
    local imagem_temp=${disco_img%/*}/temp_${RANDOM}.${disco_img##*.}
    qemu-img create -f qcow2 -o preallocation=metadata "$imagem_temp" "$novo_tamanho"
    
    virt-resize --expand "$particao" --LV-expand "$volume_logico" "$disco_img" "$imagem_temp"
    
    cat > /tmp/ajuste.sh << SCRIPT
#!/bin/bash
lvextend -l +100%FREE $volume_logico
$comando_fs $volume_logico
SCRIPT
    
    virt-customize -a "$imagem_temp" --run /tmp/ajuste.sh
    mv "$imagem_temp" "$disco_img" && echo "Disco redimensionado com sucesso."
}

Injeção de Chaves SSH

Permite acesso automático sem senha:

function injetar_chave_ssh() {
    local dominio="$1" usuario="${2:-root}" caminho_chave="${3:-~/.ssh/id_rsa.pub}"
    virt-customize -d "$dominio" --ssh-inject "$usuario:file:$caminho_chave"
}

Tags: libvirt kvm Virtualization Bash-Scripting network-configuration

Publicado em 10-9 20:18