Para de configurar VM na mão
📋 Índice
- Antes de começar: o básico que ninguém explica
- Cloud-init: o bilhete dentro da encomenda
- As fases do cloud-init
- O arquivo cloud-config
- Além do cloud-config: outros formatos de userdata
- Debugando quando dá errado
- Cloud-init no Proxmox
- Virt-customize: recheando a caixa antes de selar
- Quando usar cada um
- O combo perfeito no Proxmox
- 1. Baixar a imagem cloud oficial
- 2. Preparar com virt-customize
- 3. Criar o template no Proxmox
- 4. Clonar e configurar
- 5. Confirmar que funcionou
- Pra onde ir depois
- Conclusão
Você acabou de criar sua décima VM do mês. Instalou o sistema, configurou o usuário, copiou as chaves SSH, instalou os pacotes... tudo na mão. De novo. Se essa cena te parece familiar, você precisa conhecer duas ferramentas que vão mudar sua relação com máquinas virtuais: Cloud-init e Virt-customize.
São ferramentas diferentes, com momentos diferentes, mas que juntas formam um combo poderoso pra quem trabalha com KVM e Proxmox. Esse post cobre as duas do começo ao fim: do conceito ao comando que você vai copiar e adaptar.
Antes de começar: o básico que ninguém explica
Pra usar as duas ferramentas desse post, você vai trabalhar com imagens cloud-ready: arquivos .qcow2 que são distribuições Linux pré-instaladas, prontas pra rodar em ambientes de virtualização. A Debian, Ubuntu, Rocky Linux e outras disponibilizam essas imagens oficialmente. Elas são leves (geralmente 500MB a 2GB), vêm com cloud-init instalado e partição mínima que você expande depois.
O qcow2 é simplesamente o formato de disco virtual do QEMU. Pensa nele como um .iso, mas que já é o disco pronto, não um instalador.
💡 Por que não usar ISO normal? Dá pra usar, mas aí você precisa passar pela instalação manual toda vez. Com imagem cloud-ready, você pula direto pra configuração. Em escala, essa diferença importa muito.
Cloud-init: o bilhete dentro da encomenda
Pensa assim: você encomenda um produto e dentro da caixa tem um bilhete de instruções: "ligue aqui, configure assim, coloque isso na tomada". O Cloud-init é esse bilhete pra sua VM.
Ele roda dentro da máquina virtual, na primeira initializção, e executa tudo que você definiu antes: cria usuários, instala pacotes, aplica configurações de rede, executa scripts. É o padrão da indústria: AWS, GCP, Azure e o Proxmox com templates cloud-ready todos falam a mesma língua.
As fases do cloud-init
O cloud-init não roda tudo de uma vez. Ele tem fases, e entender isso resolve metade dos bugs:
| Fase | Quando roda | O que faz |
|---|---|---|
init |
Cedo no boot, sem rede | Detecta datasource, monta discos |
config |
Com rede disponível | Instala pacotes, cria usuários, executa módulos |
final |
No final do boot | Executa runcmd, phone_home, scripts finais |
Isso explica por que um runcmd que faz curl pra um serviço externo pode falhar se a rede não subiu ainda. Ele roda na fase final, mas dependências de rede podem não estar 100% prontas. Solução: usar --wait ou colocar um sleep 5 antes do comando crítico no runcmd. Feio, mas funciona.
O arquivo cloud-config
O formato mais comum de userdata é o cloud-config. A primeira linha tem que ser exatamente #cloud-config; não é comentário, é a assinatura que diz ao cloud-init o que ele está recebendo.
#cloud-config
# Sem essa primeira linha, o arquivo inteiro é ignorado.
# Sem erro. Sem aviso. Sem misericórdia.
# --- Pacotes ---
package_update: true
package_upgrade: true
packages:
- nginx
- git
- curl
- htop
- fail2ban
# --- Usuário ---
users:
- name: rapha
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-ed25519 AAAA... sua_chave_publica_aqui
# --- Comandos pós-boot ---
runcmd:
- systemctl enable --now nginx
- systemctl enable --now fail2ban
- echo "Configurado em $(date)" >> /var/log/cloud-init-custom.log
# --- Arquivos que você quer criar ---
write_files:
- path: /etc/motd
content: |
Acesso autorizado somente.
Configurado automaticamente via cloud-init.
- path: /etc/ssh/sshd_config.d/hardening.conf
content: |
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
⚠️ Sobre
sudo: ALL=(ALL) NOPASSWD:ALL: isso é cômodo, mas pense bem antes de usar em produção. Em homelab tá ótimo. Em servidor exposto, prefirasudo: ALL=(ALL:ALL) ALLe use senha ou autenticação por chave comsudolimitado por comando.
Além do cloud-config: outros formatos de userdata
O cloud-config é o mais comum, mas não é o único formato que o cloud-init aceita. Vale saber que existem outros:
Shell script: se a primeira linha for #!/bin/bash (ou qualquer shebang), o cloud-init executa como script normal:
#!/bin/bash
set -euo pipefail
apt-get update -q
apt-get install -y nginx curl htop
systemctl enable --now nginx
echo "Script executado em $(date)" >> /tmp/setup.log
Mais simples pra quem já conhece bash. A desvantagem é que você perde os módulos específicos do cloud-init (como write_files e gestão de usuários padronizada).
MIME multipart: pra passar vários tipos de conteúdo juntos (um cloud-config + um script, por exemplo). Raramente necessário em homelab, mas existe.
💡 Na dúvida, use cloud-config. É o formato mais documentado, com validação embutida e comportamento previsível entre distribuições.
Debugando quando dá errado
Porque vai dar errado na primeira vez. Sempre dá.
# Ver o log principal — aqui fica o erro real
cat /var/log/cloud-init-output.log
# Status detalhado de cada fase
cloud-init status --long
# Ver exatamente o que foi passado como userdata
cloud-init query userdata
# Validar um arquivo cloud-config antes de usar
cloud-init schema --config-file meu-cloud-config.yaml
# Forçar re-execução completa (SÓ EM AMBIENTE DE TESTE)
cloud-init clean --logs && cloud-init init
⚠️
cloud-init cleanem produção: apaga o estado que indica que o cloud-init já rodou. Na próxima inicialização, ele executa tudo de novo, inclusive sobrescrever configurações que você fez depois. Use só em VMs de teste.
A maioria dos problemas cai em três categorias:
#cloud-configfaltando: arquivo ignorado silenciosamente- Indentação errada no YAML: YAML é sensível a tabs vs espaços; use sempre 2 espaços
runcmdque depende de algo que ainda não existe: rede, serviço, arquivo criado por outro módulo
Pra categoria 2, valide o YAML antes de passar pra VM:
python3 -c "import yaml, sys; yaml.safe_load(sys.stdin)" < meu-cloud-config.yaml
echo $? # 0 = YAML válido, qualquer outro = problema de sintaxe
Cloud-init no Proxmox
No Proxmox, o cloud-init funciona como um drive especial adicionado à VM, aparecendo como ide2 ou scsi1 dependendo da config. Você pode configurar pelo painel web (aba "Cloud-Init" da VM) ou pela CLI:
# Adicionar drive cloud-init a uma VM existente
qm set 100 --ide2 local-lvm:cloudinit
# Configurar usuário e chave SSH
qm set 100 --ciuser rapha --sshkeys ~/.ssh/id_ed25519.pub
# IP estático
qm set 100 --ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1
# DHCP (se preferir deixar o roteador decidir)
qm set 100 --ipconfig0 ip=dhcp
# Ver o cloud-config que o Proxmox vai passar pra VM
qm cloudinit dump 100 user
O qm cloudinit dump é subestimado: mostra exatamente o que vai ser injetado, antes de iniciar a VM. Economiza tempo de debug.
💡
vmbr0é o nome padrão da bridge de rede que o Proxmox cria durante a instalação. Se você tiver mais de uma interface de rede ou uma config customizada, o nome pode ser diferente. Veja em Datacenter → Node → Network.
Virt-customize: recheando a caixa antes de selar
Se o Cloud-init é o bilhete dentro da caixa, o Virt-customize é o que você faz com a caixa antes de enviar: instalar, modificar, configurar enquanto a VM ainda está desligada.
Ele faz parte da biblioteca libguestfs e acessa diretamente o sistema de arquivos da imagem .qcow2 sem precisar ligar a VM. Isso tem uma vantagem enorme: qualquer coisa que você faz aqui fica em todos os clones: você modifica o template uma vez, herda pra sempre.
Instalando
# Debian/Ubuntu
apt install libguestfs-tools
# RHEL/Rocky/Alma/Fedora
dnf install guestfs-tools
⚠️ No Proxmox host: instale
libguestfs-toolsdiretamente no host se quiser rodar virt-customize lá. Mas atenção: o Proxmox é Debian, entãoapt install libguestfs-toolsfunciona. Só não sai instalando pacotes no host à toa; prefira rodar virt-customize numa máquina separada e copiar a imagem depois.
As flags mais úteis
# Preparar uma imagem Debian 12 cloud pra virar template
virt-customize -a debian-12-genericcloud-amd64.qcow2 \
--update \
--install "qemu-guest-agent,curl,htop,vim,fail2ban,sudo" \
--run-command "systemctl enable qemu-guest-agent" \
--run-command "systemctl enable fail2ban" \
--timezone "America/Sao_Paulo" \
--hostname "template-base"
⚠️
--selinux-relabelsó em distros com SELinux. Em Debian e Ubuntu, essa flag não faz nada útil, pois o SELinux não é padrão nelas. Use--selinux-relabelsó com Rocky Linux, AlmaLinux, RHEL e derivados.
Outras situações em que o virt-customize salva:
# Redefinir senha de root quando você esqueceu (acontece)
virt-customize -a imagem.qcow2 --root-password password:nova-senha-aqui
# Injetar chave SSH diretamente na imagem
# (Prefira fazer via cloud-init, mas às vezes não dá)
virt-customize -a imagem.qcow2 \
--ssh-inject "rapha:file:/home/rapha/.ssh/id_ed25519.pub"
# Expandir partição root antes de importar (economiza um passo depois)
virt-resize --expand /dev/sda1 imagem-original.qcow2 imagem-expandida.qcow2
# Inspecionar o conteúdo de uma imagem sem modificar nada
virt-ls -a imagem.qcow2 /etc/
virt-cat -a imagem.qcow2 /etc/os-release
💡
virt-resizeantes de importar: as imagens cloud geralmente vêm com 2-4GB de disco, o mínimo pra rodar, mas pequeno pra usar. Você pode expandir antes de importar no Proxmox comvirt-resize, ou usarqm resizedepois. O segundo é mais fácil, mas o primeiro evita que a VM suba com disco quase cheio.
O que o virt-customize NÃO faz
Não confunda com virt-install (que cria VMs) ou virt-manager (interface gráfica). O virt-customize só modifica arquivos dentro de imagens existentes. Ele não inicializa serviços, não tem acesso à rede e não sabe o que vai acontecer quando a VM ligar. Tudo que você configura aqui é estático. Para comportamento dinâmico (como hostname por instância, IP, usuário específico), use cloud-init.
Quando usar cada um
| Situação | Ferramenta |
|---|---|
| Configurar IP, usuário, SSH na criação da VM | Cloud-init |
| Preparar um template base pra clonagem em massa | Virt-customize |
| Instalar pacotes comuns a todas as VMs do parque | Virt-customize |
| Instalar pacotes que variam por instância | Cloud-init |
| Executar scripts que dependem da rede estar ativa | Cloud-init (fase final) |
| Redefinir senha de root numa imagem de emergência | Virt-customize |
| Expandir disco de uma imagem antes de importar | virt-resize |
| A VM já está rodando e precisa de ajustes | Nenhuma; use Ansible |
O combo perfeito no Proxmox
O fluxo que definitvamente uso aqui no homelab, e que parei de alterar porque simplesmente funciona:
1. Baixar a imagem cloud oficial
# Debian 12 (Bookworm)
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
# Ubuntu 24.04 LTS
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# Rocky Linux 9
wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
2. Preparar com virt-customize
virt-customize -a debian-12-genericcloud-amd64.qcow2 \
--update \
--install "qemu-guest-agent,curl,htop,fail2ban,sudo" \
--run-command "systemctl enable qemu-guest-agent" \
--run-command "systemctl enable fail2ban" \
--timezone "America/Sao_Paulo"
Isso instala o qemu-guest-agent (essencial pro Proxmox enxergar IP e estado da VM), configura timezone e já coloca fail2ban em todas as VMs que vierem desse template.
3. Criar o template no Proxmox
# Criar a VM base (ID 9000 — uso IDs altos pra templates)
qm create 9000 --name "debian-12-template" \
--memory 2048 --cores 2 \
--net0 virtio,bridge=vmbr0 \
--ostype l26
# Importar o disco
qm importdisk 9000 debian-12-genericcloud-amd64.qcow2 local-lvm
# Configurar o hardware da VM
qm set 9000 \
--scsihw virtio-scsi-pci \
--scsi0 local-lvm:vm-9000-disk-0 \
--ide2 local-lvm:cloudinit \
--boot order=scsi0 \
--serial0 socket --vga serial0 \
--agent enabled=1
# Converter em template (irreversível — teste antes de converter)
qm template 9000
💡
--agent enabled=1ativa o suporte ao qemu-guest-agent no lado do Proxmox. Sem isso, mesmo com o agente instalado na VM, o Proxmox não consegue pegar o IP nem fazer shutdown gracioso pela interface. É o tipo de coisa que você esquece uma vez e fica se perguntando por que o IP não aparece no painel.
4. Clonar e configurar
# Clonar o template (--full cria um disco independente, não linked clone)
qm clone 9000 101 --name "minha-vm" --full
# Configurar via cloud-init
qm set 101 \
--ciuser rapha \
--sshkeys ~/.ssh/id_ed25519.pub \
--ipconfig0 ip=192.168.1.101/24,gw=192.168.1.1
# Ampliar o disco pra algo utilizável (imagem base tem ~2GB)
qm resize 101 scsi0 +18G
# Ligar
qm start 101
Em menos de 2 minutos você tem uma VM nova, configurada, com chave SSH injetada e IP definido. Sem instalar nada, sem clicar em nada, sem digitar senha em nenhum momento.
5. Confirmar que funcionou
# Esperar o cloud-init terminar e verificar status
ssh rapha@192.168.1.101 "cloud-init status --wait && cloud-init status --long"
# Ver o log de execução
ssh rapha@192.168.1.101 "cat /var/log/cloud-init-output.log"
🔥 Na prática: quando tudo funciona,
cloud-init statusretornastatus: done. Quando algo deu errado, o log em/var/log/cloud-init-output.logvai te contar exatamente onde travou. É raro chegar nisso sem saber por quê.
Pra onde ir depois
Com esse fluxo funcionando, o passo natural é Ansible, pra gerenciar o que acontece depois que a VM subiu, em escala, repetível. Mas isso é assunto pra outro post.
O que você tem agora é a base: templates que você cria uma vez e clona quantas vezes precisar, com configuração injetada automaticamente e imagem já preparada. Pra homelab, isso é definitivamente suficiente. Pra ambientes maiores, é exatamente o mesmo fluxo, só com mais VMs.
Conclusão
Cloud-init e Virt-customize resolvem o mesmo problema em momentos diferentes. Entender quando usar cada um é o que separa "copiei da documentação" de "entendi o que tá acontecendo". Combinados com o sistema de templates do Proxmox, eles são a base de qualquer infraestrutura que leva automação a sério, mesmo que seja simplesamente você, gerenciando tudo do sofá, às 23h, de pijama.
A diferença entre clicar 40 vezes pra subir uma VM e rodar 5 comandos é exatamente essa dupla. Usa.