Desvendando o Cloud-init e Virt-customize: Simplificando a Configuração de Máquinas Virtuais

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.

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.

Como funciona na prática

O Cloud-init lê arquivos de configuração chamados userdata passados na criação da VM (via interface web, CLI ou API). O formato mais comum é o cloud-config:

#cloud-config
# A primeira linha DEVE ser exatamente "#cloud-config" — sem isso, o arquivo é ignorado silenciosamente.
# Não é comentário decorativo. É a assinatura do contrato.

# Atualizar sistema e instalar pacotes
package_update: true
package_upgrade: true
packages:
  - nginx
  - git
  - curl
  - htop

# Criar usuário com acesso sudo e chave SSH
users:
  - name: rapha
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... sua_chave_publica_aqui

# Executar comandos após o boot
runcmd:
  - systemctl enable nginx
  - systemctl start nginx
  - echo "VM configurada via cloud-init em $(date)" >> /var/log/cloud-init-custom.log

# Criar arquivos diretamente
write_files:
  - path: /etc/motd
    content: |
      Bem-vindo. Tudo aqui foi configurado automaticamente.
      Você não precisou clicar em nada.

Atenção real: A linha #cloud-config na primeira posição não é comentário decorativo — é a diretiva que diz ao cloud-init o que está recebendo. Sem ela, o arquivo inteiro é ignorado sem erro, sem aviso, sem misericórdia. Já perdi 20 minutos debugando isso.

Debugando quando dá errado

Porque vai dar errado na primeira vez. Sempre dá.

# Ver o log completo do cloud-init
cat /var/log/cloud-init-output.log

# Status de cada fase
cloud-init status --long

# Ver o que foi passado como userdata
cloud-init query userdata

# Forçar re-execução (útil em testes — apaga o estado e roda tudo de novo)
cloud-init clean --logs && cloud-init init

A maioria dos problemas está em uma de três coisas: a diretiva #cloud-config faltando, indentação errada no YAML, ou um comando no runcmd que falha silenciosamente porque o ambiente ainda não está completamente pronto.

No Proxmox

No Proxmox, o Cloud-init aparece como um drive especial adicionado à VM. Você configura usuário, chave SSH e IP direto pela interface web, e na primeira inicialização a VM já sobe configurada. Funciona com qualquer imagem cloud-ready (Debian, Ubuntu, Rocky, Alma...).

# Adicionar drive cloud-init a uma VM existente (via CLI)
qm set 100 --ide2 local-lvm:cloudinit

# Definir usuário e chave SSH
qm set 100 --ciuser rapha --sshkeys ~/.ssh/id_ed25519.pub

# Aplicar IP estático
qm set 100 --ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1

# Ver a config que vai ser passada pra VM
qm cloudinit dump 100 user

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. Útil para preparar templates base que vão ser clonados muitas vezes — você modifica uma vez, todos os clones herdam.

Instalando

# Debian/Ubuntu
apt install libguestfs-tools

# RHEL/Rocky/Alma
dnf install guestfs-tools

Exemplo prático

# Preparar uma imagem Debian 12 cloud para uso como template
virt-customize -a debian-12-genericcloud-amd64.qcow2 \
  --update \
  --install "qemu-guest-agent,curl,htop,vim,fail2ban" \
  --run-command "systemctl enable qemu-guest-agent" \
  --run-command "systemctl enable fail2ban" \
  --ssh-inject "root:file:/home/rapha/.ssh/id_ed25519.pub" \
  --timezone "America/Sao_Paulo" \
  --hostname "template-base" \
  --selinux-relabel

Depois de rodar isso, a imagem já tem tudo que você quer em todos os clones — sem precisar que cada VM rode um script no primeiro boot.

Outras coisas úteis que o virt-customize faz

# Redefinir senha de root (salva vidas em emergências)
virt-customize -a imagem.qcow2 --root-password password:nova-senha

# Remover cloud-init de uma imagem (quando não quer mais o comportamento cloud)
virt-customize -a imagem.qcow2 --run-command "apt-get remove -y cloud-init"

# Inspecionar o que tem dentro de uma imagem sem modificar
virt-ls -a imagem.qcow2 /etc/

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 que variam por instância Cloud-init
Instalar pacotes comuns a todas as VMs do parque Virt-customize
Executar scripts que dependem da rede estar ativa Cloud-init
Redefinir senha de root numa imagem de emergência Virt-customize
A VM já está rodando e precisa de ajustes Nenhuma das duas — 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 imagem base (Debian Cloud, Ubuntu Server, Rocky, etc.)
  2. Virt-customize para preparar o template: qemu-guest-agent, timezone, pacotes comuns, fail2ban
  3. Importar no Proxmox como template (ID alto tipo 9000, 9001...)
  4. Cloud-init cuida do resto na hora de criar cada VM: usuário, chave SSH, hostname, IP
# 1. Baixar imagem Debian 12 cloud
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2

# 2. Preparar com virt-customize
virt-customize -a debian-12-genericcloud-amd64.qcow2 \
  --install "qemu-guest-agent,curl,htop,fail2ban" \
  --run-command "systemctl enable qemu-guest-agent" \
  --run-command "systemctl enable fail2ban" \
  --timezone "America/Sao_Paulo"

# 3. Criar VM template no Proxmox
qm create 9000 --name "debian-12-template" \
  --memory 2048 --cores 2 \
  --net0 virtio,bridge=vmbr0 \
  --ostype l26

qm importdisk 9000 debian-12-genericcloud-amd64.qcow2 local-lvm

qm set 9000 \
  --scsihw virtio-scsi-pci \
  --scsi0 local-lvm:vm-9000-disk-0 \
  --ide2 local-lvm:cloudinit \
  --boot c --bootdisk scsi0 \
  --serial0 socket --vga serial0 \
  --agent enabled=1

# Converter em template (operação irreversível — clone pra testar primeiro)
qm template 9000

# 4. Clonar e configurar com cloud-init
qm clone 9000 101 --name "minha-vm" --full
qm set 101 \
  --ciuser rapha \
  --sshkeys ~/.ssh/id_ed25519.pub \
  --ipconfig0 ip=192.168.1.101/24,gw=192.168.1.1
qm resize 101 scsi0 +18G   # ampliar o disco pra algo utilizável
qm start 101

Resultado: VM nova, configurada, com disco decente, pronta pra uso — sem clicar em nada, sem digitar senha, sem copiar chave SSH na mão.

Conclusão

Cloud-init e Virt-customize resolvem problemas parecidos em momentos diferentes. Entender quando usar cada um é o que separa "copiei da documentação" de "entendi o que está acontecendo". Combinados com o sistema de templates do Proxmox, eles são a base de qualquer homelab que leva infraestrutura como código a sério — mesmo que seja só você, gerenciando tudo do sofá, às 23h, de pijama.