Do zero a um ambiente KVM funcional
📋 Índice
- Storage pools: pare de jogar disco em
/var/lib/libvirt/images - Rede: além da NAT padrão
- Backup: a parte que ninguém documenta até perder dados
- Snapshots internos (rápidos, mas limitados)
- Backup com qemu-img (o jeito certo)
- Rotação automática de backups
- Monitoramento: saber o que está acontecendo
- virt-top: htop para VMs
- virsh domstats: métricas detalhadas
- Script de alerta simples
- Integração com Prometheus (para quem usa)
- libvirtd: autenticação entre hosts
- Live migration: mover VMs sem desligar
- Organização do ambiente multi-host
- Autostart e ordem de boot
- Logs e troubleshooting
- Conclusão
Se você chegou aqui, provavelmente já leu KVM mora no seu kernel e tem VMs rodando. Talvez até tenha feito o ajuste fino de KVM: agora de verdade. Parabéns: você tem um hypervisor funcionando.
Mas "funcionando" e "funcional para uso sério" são coisas diferentes. Um ambiente KVM de verdade tem storage organizado, backup documentado, monitoramento com alertas, e pelo menos algum plano para o dia em que um host vai precisar de manutenção (e vai precisar). Esse post cobre essa parte.
💡 Pré-requisito: esse post assume que você já tem KVM/QEMU/libvirt instalado e rodando. Se não chegou lá, começa pelo KVM mora no seu kernel.
Storage pools: pare de jogar disco em /var/lib/libvirt/images
O comportamento padrão do libvirt é colocar tudo em /var/lib/libvirt/images. Funciona para começar, mas você vai sentir falta de organização na primeira vez que tiver 15 imagens sem saber qual pertence a qual VM.
Storage pools são abstrações que o libvirt usa para organizar onde ficam os volumes. Você pode ter um pool por tipo de storage (SSD, HDD, NVMe), por finalidade (VMs de produção, templates, backups), ou por qualquer critério que faça sentido pra você.
Tipos de pool
Pool de diretório: o mais simples. Um diretório no filesystem vira um pool:
# Criar pool no SSD
virsh pool-define-as \
--name ssd-vms \
--type dir \
--target /mnt/ssd/libvirt
virsh pool-build ssd-vms
virsh pool-start ssd-vms
virsh pool-autostart ssd-vms
# Criar pool separado para templates
virsh pool-define-as \
--name templates \
--type dir \
--target /mnt/ssd/templates
virsh pool-build templates
virsh pool-start templates
virsh pool-autostart templates
Pool LVM: melhor para disco dedicado. LVM dá snapshots instantâneas e volumes de tamanho exato:
# Criar VG primeiro (disco dedicado para VMs)
pvcreate /dev/sdb
vgcreate vg-vms /dev/sdb
# Definir pool apontando para o VG
virsh pool-define-as \
--name lvm-vms \
--type logical \
--source-dev /dev/sdb \
--source-name vg-vms \
--target /dev/vg-vms
virsh pool-build lvm-vms
virsh pool-start lvm-vms
virsh pool-autostart lvm-vms
⚠️ Pool LVM não suporta qcow2: volumes LVM são raw por natureza. Você perde snapshots do qemu-img mas ganha snapshots do LVM que são mais rápidos. Para a maioria dos homelabs, pool de diretório com qcow2 é mais prático.
Trabalhando com volumes
# Listar volumes de um pool
virsh vol-list ssd-vms --details
# Criar volume novo
virsh vol-create-as ssd-vms minha-vm.qcow2 50G --format qcow2
# Mover volume entre pools (precisa que a VM esteja desligada)
virsh vol-download ssd-vms minha-vm.qcow2 /tmp/minha-vm.qcow2
virsh vol-upload lvm-vms /tmp/minha-vm.qcow2 minha-vm
# Ver espaço usado vs alocado (qcow2 só ocupa o que usa)
qemu-img info /mnt/ssd/libvirt/minha-vm.qcow2
Templates: o jeito certo de não reinstalar SO toda hora
O fluxo que uso no dia a dia:
# Baixar imagem base (Debian, Ubuntu, etc.)
wget -P /mnt/ssd/templates \
https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
# Registrar no pool de templates
virsh vol-create-from templates \
<(echo '<volume><name>debian-12-base.qcow2</name><capacity unit="G">2</capacity></volume>') \
templates debian-12-genericcloud-amd64.qcow2
# Para criar uma VM nova a partir do template:
# 1. Clonar o volume (não usar o template diretamente)
virsh vol-clone --pool ssd-vms debian-12-base.qcow2 nova-vm.qcow2 --vol-pool templates
# 2. Expandir
qemu-img resize /mnt/ssd/libvirt/nova-vm.qcow2 +30G
# 3. Criar VM com a imagem clonada
virt-install \
--name nova-vm \
--memory 2048 \
--vcpus 2 \
--disk /mnt/ssd/libvirt/nova-vm.qcow2,format=qcow2 \
--os-variant debian12 \
--network network=default \
--import \
--noautoconsole
Rede: além da NAT padrão
O libvirt cria uma rede NAT padrão (virbr0, 192.168.122.0/24) que funciona para desenvolvimento. Para homelab real, você quer VMs com IPs na mesma rede do host, sem NAT, sem port forwarding.
Bridge com NetworkManager
# Criar bridge
nmcli connection add \
type bridge \
ifname br0 \
con-name br0
# Adicionar interface física à bridge
nmcli connection add \
type ethernet \
ifname eth0 \
master br0 \
con-name br0-eth0
# Ativar
nmcli connection up br0
nmcli connection up br0-eth0
# Confirmar
ip link show br0
⚠️ Cuidado com SSH remoto. Adicionar
eth0a uma bridge pode derrubar a conexão de rede momentaneamente. Faça isso com acesso físico ou via KVM/IPMI/iLO, não por SSH se for a única forma de acesso.
# Definir a rede bridge no libvirt
cat > /tmp/br0-network.xml << 'EOF'
<network>
<name>host-bridge</name>
<forward mode="bridge"/>
<bridge name="br0"/>
</network>
EOF
virsh net-define /tmp/br0-network.xml
virsh net-start host-bridge
virsh net-autostart host-bridge
Com isso, novas VMs criadas com --network network=host-bridge recebem IP diretamente do seu DHCP local e aparecem na rede como qualquer outra máquina.
VLANs no libvirt
Para ambientes com VLANs:
# Criar interface VLAN no host
nmcli connection add \
type vlan \
con-name vlan10 \
ifname eth0.10 \
dev eth0 \
id 10
# Adicionar à bridge separada
nmcli connection add \
type bridge \
ifname br-vlan10 \
con-name br-vlan10
nmcli connection modify vlan10 master br-vlan10
# Definir rede no libvirt para essa VLAN
cat > /tmp/vlan10-network.xml << 'EOF'
<network>
<name>vlan10</name>
<forward mode="bridge"/>
<bridge name="br-vlan10"/>
</network>
EOF
virsh net-define /tmp/vlan10-network.xml
virsh net-start vlan10
virsh net-autostart vlan10
Backup: a parte que ninguém documenta até perder dados
"Meu homelab não precisa de backup" é uma frase que todo mundo pensa até precisar. Veja um estratégia que funciona.
Snapshots internos (rápidos, mas limitados)
# Criar snapshot antes de uma mudança arriscada
virsh snapshot-create-as minha-vm \
snap-pre-update \
--description "antes da atualização do kernel 6.8" \
--atomic # garante consistência
# Listar
virsh snapshot-list minha-vm
# Restaurar
virsh snapshot-revert minha-vm snap-pre-update
# Deletar snapshot antigo (snapshots ocupam espaço)
virsh snapshot-delete minha-vm snap-pre-update
⚠️ Snapshots internos do libvirt têm limitações com qcow2 externo. Para VMs com discos raw ou LVM, o snapshot interno não captura o disco, apenas a configuração em memória. Sempre verificar com
virsh snapshot-list --detailsse o disco foi incluído.
Backup com qemu-img (o jeito certo)
Para backup real, com compressão e verificação de integridade:
#!/bin/bash
# backup-vm.sh — backup consistente de VMs KVM
VM_NAME="$1"
BACKUP_DIR="/mnt/hdd/backups/vms/${VM_NAME}"
DATE=$(date +%Y-%m-%d)
DISK_PATH=$(virsh domblkinfo "$VM_NAME" vda 2>/dev/null | awk '/Physical size/{print $NF}')
# Pegar o path real do disco
DISK_FILE=$(virsh dumpxml "$VM_NAME" | grep "<source file" | head -1 | sed "s/.*file='//;s/'.*//")
if [ -z "$DISK_FILE" ]; then
echo "Disco não encontrado para $VM_NAME"
exit 1
fi
mkdir -p "$BACKUP_DIR"
# Opção 1: VM desligada — cópia direta comprimida
if virsh domstate "$VM_NAME" | grep -q "shut off"; then
echo "VM desligada, fazendo backup direto..."
qemu-img convert \
-f qcow2 \
-O qcow2 \
-c \
"$DISK_FILE" \
"${BACKUP_DIR}/${DATE}.qcow2"
else
# Opção 2: VM rodando — snapshot externo + cópia + merge
echo "VM rodando, usando snapshot externo..."
SNAP_FILE="${BACKUP_DIR}/${DATE}.snap"
# Criar snapshot externo (não pausa a VM, mas cria um ponto consistente)
virsh snapshot-create-as "$VM_NAME" \
"backup-${DATE}" \
--disk-only \
--diskspec vda,snapshot=external,file="${SNAP_FILE}" \
--atomic
# Copiar o disco base (que agora está frozen)
qemu-img convert -f qcow2 -O qcow2 -c \
"$DISK_FILE" \
"${BACKUP_DIR}/${DATE}.qcow2"
# Fazer blockcommit para voltar ao estado normal (merge do snapshot de volta)
virsh blockcommit "$VM_NAME" vda --active --pivot --wait
# Remover snapshot temporário do libvirt
virsh snapshot-delete "$VM_NAME" "backup-${DATE}" --metadata
rm -f "${SNAP_FILE}"
fi
# Verificar integridade do backup
qemu-img check "${BACKUP_DIR}/${DATE}.qcow2"
echo "Backup concluído: ${BACKUP_DIR}/${DATE}.qcow2"
# Guardar XML da VM também (configurção, não só disco)
virsh dumpxml "$VM_NAME" > "${BACKUP_DIR}/${DATE}.xml"
💡 Sempre fazer backup do XML junto com o disco. Com o XML você recria a VM exatamente como estava: mesma topologia de CPU, mesma rede, mesmo mapeamento de dispositivos. Sem o XML, você tem o dado mas perde a configuração.
Rotação automática de backups
# Adicionar ao crontab (cron -e)
# Backup diário das VMs críticas, às 2h da manhã
0 2 * * * /usr/local/bin/backup-vm.sh minha-vm >> /var/log/backup-vms.log 2>&1
# Limpeza semanal — manter apenas últimos 7 dias
0 3 * * 0 find /mnt/hdd/backups/vms -name "*.qcow2" -mtime +7 -delete
Monitoramento: saber o que está acontecendo
virt-top: htop para VMs
# Instalar
apt install virt-top # Debian/Ubuntu
dnf install virt-top # Fedora/RHEL
# Usar
virt-top
# Keybindings:
# 1 = ordenar por CPU
# 2 = ordenar por memória
# 3 = ordenar por I/O de disco
# d = toggle de domínios
# q = sair
virsh domstats: métricas detalhadas
# Métricas completas de todas as VMs rodando
virsh domstats
# VM específica, só CPU e memória
virsh domstats minha-vm --cpu-total --balloon
# Saída em formato parseable (útil para scripts)
virsh domstats minha-vm --raw
# I/O de disco
virsh domblkstat minha-vm vda
# I/O de rede
virsh domifstat minha-vm vnet0
# Informações de memória (balloon driver)
virsh dommemstat minha-vm
Script de alerta simples
#!/bin/bash
# check-vms.sh — alerta se alguma VM crítica foi desligada inesperadamente
CRITICAL_VMS=("web-server" "db-primary" "vpn-gateway")
WEBHOOK_URL="https://hooks.slack.com/your-webhook-url"
for vm in "${CRITICAL_VMS[@]}"; do
state=$(virsh domstate "$vm" 2>/dev/null)
if [ "$state" != "running" ]; then
message="⚠️ VM ${vm} não está rodando! Estado: ${state}"
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d "{\"text\": \"${message}\"}"
fi
done
# Rodar a cada 5 minutos via cron
*/5 * * * * /usr/local/bin/check-vms.sh
Integração com Prometheus (para quem usa)
# prometheus-libvirt-exporter expõe métricas de VMs via HTTP
docker run -d \
--name libvirt-exporter \
--privileged \
-v /var/run/libvirt:/var/run/libvirt:ro \
-p 9177:9177 \
alekseizakharov/libvirt-exporter
# Métricas disponíveis em http://localhost:9177/metrics
# libvirt_domain_info_cpu_time_seconds_total
# libvirt_domain_info_memory_usage_bytes
# libvirt_domain_block_stats_read_bytes_total
libvirtd: autenticação entre hosts
Por padrão, o libvirt usa socket Unix local; seguro, mas limitado a um host. Para gerenciar múltiplos hosts ou fazer live migration, você precisa autenticação remota.
Via SSH (mais simples, sem configuração adicional)
# Conectar no libvirt de outro host via SSH
virsh -c qemu+ssh://usuario@host-2/system list
# Funciona se você tem acesso SSH ao host-2
# Usa o agente SSH local para autenticação
export LIBVIRT_DEFAULT_URI="qemu+ssh://usuario@host-2/system"
virsh list --all
Via TCP com SASL (para automação sem senha)
# Em /etc/libvirt/libvirtd.conf no host remoto
listen_tls = 0
listen_tcp = 1
tcp_port = "16509"
auth_tcp = "sasl"
# Criar usuário SASL
saslpasswd2 -a libvirt usuario-admin
sasldblistusers2 -f /etc/libvirt/passwd.db
# Reiniciar com listen habilitado
systemctl restart libvirtd
# No /etc/default/libvirtd (ou /etc/sysconfig/libvirtd)
LIBVIRTD_ARGS="--listen"
⚠️ SASL sem TLS ainda trafega dados em texto claro. Para produção real, use TLS ou restrinja por firewall. Para homelab na rede local, SASL+TCP é aceitável; é o que o próprio Proxmox usa internamente para comunicação entre nós.
Live migration: mover VMs sem desligar
Com dois hosts KVM na mesma rede e storage compartilhado (ou com cópia de storage), dá para mover VMs sem interrupção perceptível.
Pré-requisitos
# Em ambos os hosts: SSH sem senha entre eles
ssh-copy-id usuario@host-2
# Confirmar que libvirtd aceita conexões remotas (ver seção anterior)
# Verificar que as VMs estão visíveis dos dois lados
virsh -c qemu+ssh://host-2/system list
Migração com storage compartilhado (NFS/Ceph)
# NFS simples: exportar o diretório de VMs
# Em /etc/exports do host-1:
/mnt/ssd/libvirt 192.168.1.0/24(rw,sync,no_subtree_check)
# No host-2: montar o mesmo storage
mount -t nfs host-1:/mnt/ssd/libvirt /mnt/ssd/libvirt
# Com storage compartilhado, migração ao vivo é direta:
virsh migrate \
--live \
--persistent \
minha-vm \
qemu+ssh://host-2/system
# A VM continua rodando durante toda a migração
# Downtime típico: < 1 segundo (só o estado da CPU/memória)
Migração sem storage compartilhado
# Copia o disco inteiro durante a migração — mais lento
virsh migrate \
--live \
--persistent \
--copy-storage-all \
minha-vm \
qemu+ssh://host-2/system
# Para VMs grandes ou com muito I/O de disco, pode ser problemático
# Migração offline é mais confiável nesses casos:
virsh migrate \
--offline \
--persistent \
minha-vm \
qemu+ssh://host-2/system \
--desturi qemu+ssh://host-2/system
💡 Live migration sem storage compartilhado usa copy-on-write incremental: começa copiando o disco base e vai sincronizando as páginas modificadas. Em VMs com muito escrita de disco (bancos de dados), a migração pode nunca convergir porque o disco muda mais rápido do que é copiado. Nesses casos, pausar a VM brevemente durante a migração ou usar migração offline.
Organização do ambiente multi-host
Conforme o número de hosts cresce, algumas práticas ajudam a não perder o fio da meada:
# ~/.bashrc — aliases para hosts
alias kvm1="virsh -c qemu+ssh://kvm1.lan/system"
alias kvm2="virsh -c qemu+ssh://kvm2.lan/system"
alias kvm3="virsh -c qemu+ssh://kvm3.lan/system"
# Ver todas as VMs de todos os hosts de uma vez
for host in kvm1 kvm2 kvm3; do
echo "=== $host ==="
virsh -c qemu+ssh://${host}.lan/system list --all
done
# Exportar XML de todas as VMs para backup de configuração
backup_configs() {
local host="$1"
local output_dir="/mnt/hdd/configs/${host}"
mkdir -p "$output_dir"
for vm in $(virsh -c qemu+ssh://${host}.lan/system list --all --name); do
virsh -c qemu+ssh://${host}.lan/system dumpxml "$vm" \
> "${output_dir}/${vm}.xml"
done
echo "Configs do $host salvas em $output_dir"
}
backup_configs kvm1
backup_configs kvm2
Autostart e ordem de boot
# VM sobe automaticamente com o host
virsh autostart minha-vm
# Desabilitar autostart
virsh autostart minha-vm --disable
# Ver quais VMs têm autostart habilitado
virsh list --all --autostart
# O libvirt não garante ordem de boot entre VMs com autostart
# Para controlar ordem, usar oneshot systemd units ou script de boot:
cat > /etc/systemd/system/vm-startup.service << 'EOF'
[Unit]
Description=Start KVM VMs in order
After=libvirtd.service
Requires=libvirtd.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/start-vms-ordered.sh
[Install]
WantedBy=multi-user.target
EOF
#!/bin/bash
# start-vms-ordered.sh
# Infraestrutura primeiro
virsh start dns-vm && sleep 5
virsh start router-vm && sleep 10
# Depois os serviços que dependem de rede
virsh start web-server
virsh start db-primary
Logs e troubleshooting
# Logs do libvirtd
journalctl -u libvirtd -f
# Log de cada VM (fica em /var/log/libvirt/qemu/)
tail -f /var/log/libvirt/qemu/minha-vm.log
# Verificar integridade de um disco qcow2
qemu-img check /mnt/ssd/libvirt/minha-vm.qcow2
# Ver o que está acontecendo no QEMU de uma VM
virsh qemu-monitor-command minha-vm --hmp "info status"
virsh qemu-monitor-command minha-vm --hmp "info block"
# Forçar dump de core da VM (diagnóstico de pânico)
virsh dump minha-vm /tmp/vm-core.dump
Conclusão
A diferença entre "tenho VMs rodando" e "tenho um ambiente KVM" está nesses detalhes: storage organizado em pools, backups testados e automatizados, monitoramento que avisa antes de você descobrir o problema, e pelo menos algum plano para mover workloads quando um host precisa de atenção.
Não precisa implementar tudo de uma vez. Começa com o backup, definitvamente o item mais crítico e o mais frequentemente ignorado. Com backups funcionando, o resto você vai adicionando conforme a necessidade aparece.
O próximo passo natural daqui é o Proxmox: O ESXi que não te manda boleto, que entrega boa parte dessa infraestrutura com interface web e cluster gerenciado. Mas agora você sabe o que está acontecendo por baixo, e isso muda tudo quando algo quebra.