Criação de Template Debian 12 QEMU/KVM com Packer
Introdução
A criação de máquinas virtuais é uma tarefa comum em ambientes de TI modernos, seja para desenvolvimento, testes ou produção. No entanto, configurar manualmente cada máquina virtual pode ser um processo demorado e propenso a erros. É nesse contexto que ferramentas de automação como o Packer se tornam essenciais, permitindo a criação de templates de máquinas virtuais de forma padronizada, reproduzível e eficiente.
O Packer, desenvolvido pela HashiCorp, é uma ferramenta de código aberto que automatiza a criação de imagens de máquinas virtuais para múltiplas plataformas a partir de uma única configuração. Com ele, podemos definir todo o processo de instalação e configuração em arquivos de código, seguindo o princípio de Infraestrutura como Código (IaC). Isso não apenas economiza tempo, mas também garante consistência entre ambientes e facilita a documentação e o versionamento das configurações.
Neste tutorial, vamos explorar como criar um template do Debian 12 (Bookworm) para uso com QEMU/KVM, uma das tecnologias de virtualização mais populares em ambientes Linux. Vamos utilizar o Packer para automatizar todo o processo de instalação e configuração, resultando em uma imagem pronta para uso que pode ser facilmente replicada e personalizada conforme necessário. Ao final, também veremos como testar nossa imagem utilizando o Terraform, outra ferramenta poderosa da HashiCorp para gerenciamento de infraestrutura.
Pré-requisitos
Antes de iniciarmos o processo de criação do template, é importante garantir que você tenha o ambiente adequado e os conhecimentos básicos necessários. Para seguir este tutorial, você precisará de:
- Um sistema Linux (utilizaremos Ubuntu como exemplo, mas os conceitos se aplicam a outras distribuições)
- Acesso de superusuário (root) ou permissões sudo
- Conhecimentos básicos de linha de comando Linux
- Familiaridade com conceitos de virtualização
- Conexão com a internet para download de pacotes e imagens
As ferramentas que utilizaremos incluem:
- QEMU/KVM: como plataforma de virtualização
- Libvirt: para gerenciamento de máquinas virtuais
- Packer: para automação da criação de imagens
- Terraform: para testar o template criado
- Cloud-init: para configuração inicial da máquina virtual
Instalação e Configuração do KVM/LIBVIRT
O KVM (Kernel-based Virtual Machine) é uma tecnologia de virtualização integrada ao kernel Linux que permite executar máquinas virtuais com desempenho próximo ao nativo. O LIBVIRT é uma API e um conjunto de ferramentas para gerenciar plataformas de virtualização, incluindo o KVM. Vamos instalar e configurar esses componentes.
Verificando Suporte à Virtualização
Antes de instalar o KVM, vamos verificar se o seu processador suporta virtualização:
1
egrep -c '(vmx|svm)' /proc/cpuinfo
Se o resultado for maior que 0, seu processador suporta virtualização. Você também pode verificar se a virtualização está habilitada na BIOS/UEFI com o seguinte comando:
1
kvm-ok
Se você receber uma mensagem indicando que o KVM não está disponível, pode ser necessário habilitar a virtualização na BIOS/UEFI do seu sistema.
Instalando o KVM e o LIBVIRT
Vamos instalar os pacotes necessários para o KVM e o LIBVIRT no Ubuntu:
1
2
sudo apt-get update
sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager
Estes pacotes incluem:
- qemu-kvm: O emulador QEMU com aceleração KVM
- libvirt-daemon-system: O daemon libvirt e seus scripts de inicialização
- libvirt-clients: Ferramentas de linha de comando para gerenciar máquinas virtuais
- bridge-utils: Utilitários para configuração de bridges de rede
- virtinst: Ferramentas para criação de máquinas virtuais
- virt-manager: Interface gráfica para gerenciamento de máquinas virtuais (opcional, mas útil)
Configurando Permissões
Para gerenciar máquinas virtuais sem privilégios de root, adicione seu usuário aos grupos libvirt e kvm:
1
2
sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER
Após executar esses comandos, faça logout e login novamente para que as alterações de grupo tenham efeito.
Verificando a Instalação
Verifique se o serviço libvirt está em execução:
1
sudo systemctl status libvirtd
Você deve ver uma saída indicando que o serviço está ativo (running). Você também pode listar as máquinas virtuais existentes (provavelmente nenhuma ainda) com:
1
virsh list --all
Configurando a Rede Default
O libvirt cria por padrão uma rede virtual chamada “default” que utiliza NAT para permitir que as VMs acessem a internet. Vamos verificar se esta rede está ativa:
1
virsh net-list --all
Se a rede “default” não estiver ativa, você pode ativá-la com:
1
2
virsh net-start default
virsh net-autostart default
A rede “default” geralmente utiliza o range de IPs 192.168.122.0/24, com o host atuando como gateway no endereço 192.168.122.1.
Configurando o Armazenamento
O libvirt utiliza pools de armazenamento para gerenciar os discos das máquinas virtuais. Por padrão, existe um pool chamado “default” localizado em /var/lib/libvirt/images/. Vamos verificar se este pool está ativo:
1
virsh pool-list --all
Se o pool “default” não estiver ativo, você pode ativá-lo com:
1
2
virsh pool-start default
virsh pool-autostart default
Com isso, o KVM e o LIBVIRT estão instalados e configurados, prontos para serem utilizados pelo Packer e pelo Terraform.
Instalação e Configuração do Terraform
O Terraform é uma ferramenta de infraestrutura como código (IaC) que permite definir e provisionar infraestrutura de forma declarativa. Vamos instalá-lo e configurá-lo para trabalhar com o libvirt.
Instalando o Terraform
Assim como o Packer, o Terraform é desenvolvido pela HashiCorp e pode ser instalado a partir do repositório oficial:
1
2
3
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform
Verifique a instalação com:
1
terraform version
Você deve ver a versão do Terraform instalada em seu sistema.
Instalando o Provider Libvirt para Terraform
O Terraform utiliza providers para interagir com diferentes plataformas. Para trabalhar com o libvirt, precisamos do provider dmacvicar/libvirt. Este provider não é instalado separadamente; o Terraform o baixará automaticamente quando inicializarmos um diretório de trabalho que o utilize.
No entanto, o provider libvirt tem algumas dependências que precisamos instalar:
1
sudo apt-get install -y libvirt-dev
Configurando o Ambiente para o Provider Libvirt
Para que o provider libvirt funcione corretamente, pode ser necessário configurar algumas variáveis de ambiente. Crie ou edite o arquivo ~/.bashrc e adicione as seguintes linhas:
1
export LIBVIRT_DEFAULT_URI="qemu:///system"
Em seguida, aplique as alterações:
1
source ~/.bashrc
Esta configuração define o URI padrão para conexão com o libvirt, utilizando o QEMU/KVM com privilégios de sistema.
Criando uma Chave SSH para Acesso às VMs
Para acessar as máquinas virtuais que criaremos com o Terraform, é útil configurar uma chave SSH. Se você ainda não tem um par de chaves SSH, crie um:
1
ssh-keygen -t rsa -b 4096 -f ~/.ssh/tfvms
Este comando cria um par de chaves RSA de 4096 bits, salvando a chave privada em ~/.ssh/tfvms e a chave pública em ~/.ssh/tfvms.pub. A chave pública será utilizada pelo cloud-init para configurar o acesso SSH às máquinas virtuais.
Com o Terraform instalado e configurado, estamos prontos para utilizá-lo para testar nossos templates de máquinas virtuais.
Instalação do Packer no Ubuntu
O próximo passo é instalar o Packer, a ferramenta que utilizaremos para criar nossos templates de máquinas virtuais. A HashiCorp mantém um repositório oficial de pacotes que facilita a instalação e atualização de suas ferramentas. Vamos adicionar esse repositório ao nosso sistema e instalar o Packer.
Começamos adicionando a chave GPG do repositório HashiCorp:
1
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
Em seguida, adicionamos o repositório ao nosso sistema:
1
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
O comando lsb_release -cs
retorna o nome da versão da sua distribuição Ubuntu (como “focal” para Ubuntu 20.04 ou “jammy” para Ubuntu 22.04), garantindo que você adicione o repositório correto para sua versão específica.
Agora, atualizamos a lista de pacotes e instalamos o Packer:
1
sudo apt-get update && sudo apt-get install packer
Para verificar se a instalação foi bem-sucedida, execute:
1
packer version
Você deverá ver a versão do Packer instalada em seu sistema. Para este tutorial, recomendamos utilizar a versão 1.10.0 ou superior, conforme especificado em nossos templates.
Preparação do Ambiente de Trabalho
Com todas as ferramentas necessárias instaladas, vamos criar a estrutura de diretórios para nosso projeto. Uma boa organização é fundamental para manter o trabalho limpo e facilitar futuras modificações ou expansões.
Vamos criar um diretório para nossos projetos Packer, com subdiretórios específicos para o tipo de virtualização (KVM) e a distribuição (Debian 12):
1
2
mkdir -p ~/packer/kvm/deb12
cd ~/packer/kvm/deb12
Esta estrutura nos permite organizar diferentes projetos de forma lógica. Se no futuro quisermos criar templates para outras distribuições ou plataformas de virtualização, podemos simplesmente adicionar novos diretórios mantendo a mesma estrutura organizacional.
Dentro do diretório deb12
, criaremos todos os arquivos necessários para nosso projeto: o template principal do Packer, o script de limpeza e o arquivo de pré-configuração (preseed) para automação da instalação do Debian.
Criação do Template Packer
O coração do nosso projeto é o arquivo de template do Packer, que define todo o processo de criação da imagem. Vamos criar um arquivo chamado os-install.pkr.hcl
usando seu editor de texto preferido:
1
vim os-install.pkr.hcl
Agora, vamos analisar o conteúdo deste arquivo em detalhes:
1
2
3
4
5
6
7
8
9
packer {
required_version = ">= 1.10.0"
required_plugins {
qemu = {
version = "= 1.1.0"
source = "github.com/hashicorp/qemu"
}
}
}
Esta primeira seção define os requisitos do Packer. Especificamos que precisamos da versão 1.10.0 ou superior do Packer e do plugin QEMU na versão exata 1.1.0. O plugin QEMU é necessário para interagir com o QEMU/KVM durante o processo de build.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
variable "vm_name" {
default = "debian-kvm-template.raw"
}
variable "disk_size" {
default = "8192"
}
variable "iso_url" {
default = "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.7.0-amd64-netinst.iso"
}
variable "iso_checksum" {
default = "8fde79cfc6b20a696200fc5c15219cf6d721e8feb367e9e0e33a79d1cb68fa83"
}
Nesta seção, definimos variáveis que serão utilizadas ao longo do template. Isso facilita a manutenção e personalização do processo de build. As variáveis incluem:
vm_name
: Nome do arquivo de imagem que será geradodisk_size
: Tamanho do disco virtual em MB (8GB neste caso)iso_url
: URL para download da ISO do Debian 12iso_checksum
: Checksum SHA256 da ISO para verificação de integridade
É importante sempre verificar o checksum da ISO para garantir que estamos utilizando uma imagem íntegra e oficial. Você pode encontrar o checksum mais recente no site oficial do Debian.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
source "qemu" "iso" {
vm_name = var.vm_name
iso_url = var.iso_url
iso_checksum = var.iso_checksum
disk_size = var.disk_size
memory = 1024
disk_image = false
output_directory = "build/os-base"
disk_interface = "virtio"
net_device = "virtio-net"
format = "raw"
headless = false
accelerator = "kvm"
ssh_username = "debian"
ssh_password = "debian"
ssh_wait_timeout = "20m"
shutdown_command = "echo 'debian' | sudo -S shutdown -P now"
http_directory = "http"
boot_wait = "3s"
boot_command = [
"<esc><wait>",
"install auto=true priority=critical ",
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg<enter>"
]
}
Esta seção define a fonte (source) para o build, que é a configuração da máquina virtual QEMU que será utilizada. Vamos analisar os principais parâmetros:
vm_name
,iso_url
,iso_checksum
edisk_size
: Utilizam as variáveis definidas anteriormentememory
: Define a quantidade de memória RAM para a VM (1GB)disk_image
: Indica se estamos partindo de uma imagem de disco existente (false, pois estamos criando uma nova)output_directory
: Diretório onde a imagem será salvadisk_interface
enet_device
: Utilizam drivers virtio para melhor desempenhoformat
: Formato da imagem de saída (raw)headless
: Se false, mostra a interface gráfica durante o build; se true, executa em modo headlessaccelerator
: Utiliza KVM para aceleração de hardware
Os parâmetros relacionados a SSH são utilizados pelo Packer para se conectar à VM após a instalação do sistema operacional:
ssh_username
essh_password
: Credenciais para acesso SSHssh_wait_timeout
: Tempo máximo de espera pela disponibilidade do SSH
O http_directory
especifica o diretório onde o arquivo preseed.cfg estará disponível. O Packer inicia um servidor HTTP temporário durante o build para disponibilizar este arquivo.
O boot_command
é uma sequência de comandos que serão enviados à VM durante o boot para iniciar a instalação automatizada. Neste caso, estamos pressionando ESC para acessar o menu de boot do Debian, e então passando os parâmetros para uma instalação automatizada utilizando o arquivo preseed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
build {
name = "iso"
sources = ["source.qemu.iso"]
provisioner "file" {
source = "cleanup.sh"
destination = "/tmp/cleanup.sh"
}
provisioner "shell" {
inline = [
"chmod +x /tmp/cleanup.sh",
"echo 'packer' | sudo -S /tmp/cleanup.sh"
]
}
}
A seção final define o processo de build propriamente dito. Especificamos que vamos utilizar a fonte “qemu.iso” definida anteriormente e configuramos dois provisionadores:
- Um provisionador “file” que copia o script cleanup.sh para a VM
- Um provisionador “shell” que torna o script executável e o executa com privilégios de superusuário
O script cleanup.sh será responsável por realizar configurações adicionais e limpeza do sistema antes de finalizar a imagem.
Script de Limpeza e Preparação
Agora, vamos criar o script de limpeza e preparação que será executado dentro da VM durante o processo de build. Este script é fundamental para configurar o sistema adequadamente para uso como template.
1
vim cleanup.sh
Vamos analisar o conteúdo deste script em detalhes:
1
2
3
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
O cabeçalho do script define o interpretador bash e configura opções para tornar o script mais robusto:
set -e
: Faz o script terminar se qualquer comando falharset -u
: Trata variáveis não definidas como erroset -o pipefail
: Faz com que um pipeline retorne erro se qualquer comando falharIFS=$'\n\t'
: Altera o separador de campos interno para quebras de linha e tabulações, tornando o processamento de texto mais seguro
1
2
3
4
5
6
7
# Instalar pacotes essenciais para nuvem
DEBIAN_FRONTEND=noninteractive apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends \
cloud-init \
netplan.io \
systemd-resolved \
--yes
Esta seção instala pacotes essenciais para ambientes de nuvem:
cloud-init
: Ferramenta padrão para inicialização e configuração de instâncias em nuvemnetplan.io
: Sistema moderno de configuração de rede para Ubuntu e Debiansystemd-resolved
: Serviço de resolução de nomes do systemd
A variável DEBIAN_FRONTEND=noninteractive
garante que a instalação ocorra sem interação do usuário, essencial para automação. A opção --no-install-recommends
reduz o número de pacotes instalados, incluindo apenas os estritamente necessários.
1
2
3
4
5
6
7
8
# Configurar Netplan
cat <<EOF > /etc/netplan/50-cloud-init.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: true
EOF
Aqui, criamos uma configuração básica do Netplan que configura a interface de rede eth0 para usar DHCP. O Netplan é o sistema moderno de configuração de rede no Debian e Ubuntu, que utiliza arquivos YAML para definir a configuração de rede.
1
2
3
4
5
6
7
8
9
10
11
12
# Reconfigurar cloud-init
cat <<EOF > /etc/cloud/cloud.cfg.d/91-debian-user.cfg
system_info:
distro: debian
default_user:
name: debian
lock_passwd: false
gecos: Debian
groups: [adm, audio, cdrom, dialout, dip, floppy, netdev, plugdev, sudo, video]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash
EOF
Esta seção configura o cloud-init, especificando detalhes sobre o usuário padrão que será criado quando uma nova instância for iniciada a partir deste template. O usuário “debian” terá acesso sudo sem senha e pertencerá a vários grupos úteis.
1
2
3
4
5
6
7
8
# Ativar serviços essenciais
systemctl enable systemd-resolved.service
# Configurar link simbólico para o resolv.conf gerenciado pelo systemd-resolved
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
# Adicionar cloud-init ao multi-user target
systemctl add-wants multi-user.target cloud-init.target
Aqui, ativamos o serviço systemd-resolved para resolução de nomes, configuramos o /etc/resolv.conf como um link simbólico para o arquivo gerenciado pelo systemd-resolved, e garantimos que o cloud-init seja iniciado automaticamente no nível multi-user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Configuração do GRUB
cat <<EOF > /etc/default/grub
# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
# info -f grub -n 'Simple configuration'
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0"
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200"
EOF
Esta configuração do GRUB é especialmente importante para ambientes de nuvem e virtualização. Configuramos o GRUB para enviar a saída tanto para o console gráfico (tty0) quanto para o console serial (ttyS0), o que é essencial para acessar a VM via console serial em ambientes de nuvem. A velocidade do console serial é definida como 115200 baud.
1
2
3
4
5
6
# Limpeza de pacotes desnecessários e configuração de rede antiga
DEBIAN_FRONTEND=noninteractive apt-get autoremove --yes --purge ifupdown
DEBIAN_FRONTEND=noninteractive apt-get autoremove --yes --purge $(dpkg -l "linux-image*" | grep "^ii" | grep -v linux-image-amd64 | head -n -1 | cut -d " " -f 3)
DEBIAN_FRONTEND=noninteractive apt-get clean
find /var/cache/apt /var/lib/apt /var/lib/dhcp /var/log -mindepth 1 -print -delete
rm -vf /etc/network/interfaces /etc/network/interfaces.d/50-cloud-init.cfg /etc/adjtime /etc/hostname /etc/hosts /etc/ssh/*key* /var/cache/ldconfig/aux-cache /var/lib/systemd/random-seed
Esta seção realiza uma limpeza extensiva do sistema:
- Remove o pacote ifupdown (sistema antigo de configuração de rede)
- Remove imagens de kernel antigas, mantendo apenas a mais recente
- Limpa o cache do apt
- Remove arquivos de log e cache
- Remove arquivos de configuração específicos da instalação, como hostname e chaves SSH
1
2
3
4
5
6
7
8
9
10
11
# Zerar o /etc/machine-id para que um novo seja gerado na primeira inicialização
truncate -s 0 /etc/machine-id
touch /var/log/lastlog
chown root:utmp /var/log/lastlog
chmod 664 /var/log/lastlog
# Liberar blocos de armazenamento não utilizados
fstrim --all --verbose
# Remover o próprio script após execução
rm -f $(readlink -f $0)
As etapas finais incluem:
- Zerar o machine-id para que cada nova instância receba um ID único
- Configurar o arquivo lastlog corretamente
- Executar fstrim para liberar blocos de armazenamento não utilizados, o que pode reduzir significativamente o tamanho da imagem final
- Remover o próprio script de limpeza
Estas etapas são essenciais para garantir que cada nova instância criada a partir deste template seja única e não carregue configurações específicas da instalação original.
Arquivo de Pré-configuração (Preseed)
O arquivo preseed.cfg é fundamental para automatizar a instalação do Debian, permitindo que todo o processo ocorra sem intervenção manual. Vamos criar o diretório http e o arquivo preseed.cfg:
1
2
mkdir http
vim http/preseed.cfg
Agora, vamos analisar o conteúdo deste arquivo em detalhes:
# Definindo o idioma como português do Brasil
d-i debian-installer/locale string pt_BR.UTF-8
# Teclado para layout brasileiro
d-i keyboard-configuration/xkb-keymap select br
# Locales adicionais
d-i localechooser/supported-locales multiselect pt_BR.UTF-8, en_US.UTF-8
Esta primeira seção configura as opções regionais, definindo o idioma como português do Brasil, o layout de teclado como brasileiro (ABNT2) e habilitando os locales pt_BR.UTF-8 e en_US.UTF-8.
### Network configuration
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string packer
d-i netcfg/get_domain string local
A seção de configuração de rede define que o instalador deve selecionar automaticamente a interface de rede, e configura o hostname como “packer” e o domínio como “local”. Estas configurações são temporárias e serão substituídas pelo cloud-init quando uma nova instância for criada.
### Mirror settings
d-i mirror/country string BR
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
Aqui, configuramos os mirrors do Debian para download de pacotes. Definimos o país como Brasil (BR), o hostname do mirror como deb.debian.org e o diretório como /debian. A linha de proxy está vazia, indicando conexão direta sem proxy.
### Account setup
# Senha root e usuário padrão
d-i passwd/root-password password root
d-i passwd/root-password-again password root
d-i passwd/user-fullname string debian
d-i passwd/username string debian
d-i passwd/user-password password debian
d-i passwd/user-password-again password debian
Esta seção configura as contas de usuário. Definimos a senha do root como “root” e criamos um usuário chamado “debian” com senha “debian”. Em um ambiente de produção, estas senhas seriam substituídas pelo cloud-init ou outro mecanismo de provisionamento.
# Fuso horário do Brasil
d-i time/zone string America/Sao_Paulo
d-i clock-setup/utc boolean true
Configuramos o fuso horário para America/Sao_Paulo (horário de Brasília) e definimos que o relógio do hardware deve usar UTC.
# Particionamento Automático - LVM
d-i partman-auto/disk string /dev/vda
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
# Forçar o particionamento e gravação no disco sem confirmação
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
Esta seção configura o particionamento automático usando LVM (Logical Volume Manager). Especificamos /dev/vda como o disco a ser particionado (padrão para discos virtuais no QEMU/KVM) e configuramos para usar LVM. As opções adicionais garantem que o particionamento ocorra sem intervenção manual, confirmando automaticamente todas as etapas.
### Base system installation
d-i base-installer/install-recommends boolean false
# Definindo para usar atualização completa após a instalação
d-i pkgsel/upgrade select full-upgrade
Aqui, configuramos a instalação do sistema base para não incluir pacotes recomendados (reduzindo o tamanho da instalação) e especificamos que uma atualização completa deve ser realizada após a instalação.
# Escolha de pacotes
tasksel tasksel/first multiselect SSH server
d-i pkgsel/include string haveged openssh-server sudo
Selecionamos o perfil “SSH server” e especificamos pacotes adicionais a serem instalados:
- haveged: Gerador de entropia, útil para melhorar a geração de chaves SSH
- openssh-server: Servidor SSH para acesso remoto
- sudo: Permite execução de comandos com privilégios de superusuário
### Boot loader installation
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/bootdev string default
### Finishing up the installation
d-i finish-install/reboot_in_progress note
# Comando de finalização
d-i preseed/late_command string echo 'debian ALL = (root) NOPASSWD: ALL' > /target/etc/sudoers.d/debian
As seções finais configuram a instalação do GRUB e definem um comando a ser executado ao final da instalação. O comando late_command cria um arquivo em /etc/sudoers.d/ que permite ao usuário “debian” executar comandos sudo sem senha, o que é necessário para o Packer se conectar e provisionar a VM após a instalação.
Execução do Build
Com todos os arquivos configurados, estamos prontos para executar o build da nossa imagem. Primeiro, precisamos inicializar o Packer para baixar os plugins necessários:
1
packer init .
Este comando lê o arquivo os-install.pkr.hcl e baixa o plugin QEMU especificado na seção required_plugins.
Em seguida, podemos iniciar o build propriamente dito:
1
PACKER_LOG=1 packer build os-install.pkr.hcl
A variável de ambiente PACKER_LOG=1 ativa o modo de log detalhado, o que é útil para diagnosticar problemas caso ocorram. O comando packer build inicia o processo de build conforme definido no arquivo os-install.pkr.hcl.
Durante o build, o Packer realizará as seguintes etapas:
- Download da ISO do Debian (se ainda não estiver em cache)
- Criação de uma máquina virtual QEMU/KVM temporária
- Inicialização da VM com a ISO e execução do boot_command
- Instalação automatizada do Debian usando o arquivo preseed.cfg
- Espera pela disponibilidade do SSH
- Execução dos provisionadores (cópia e execução do script cleanup.sh)
- Desligamento da VM
- Empacotamento da imagem resultante
Este processo pode levar alguns minutos, dependendo da velocidade do seu sistema e da sua conexão com a internet. Ao final, você terá uma imagem raw no diretório build/os-base/.
Conversão da Imagem
Após a conclusão do build, temos uma imagem no formato raw. Embora este formato seja eficiente para uso direto, o formato qcow2 (QEMU Copy-On-Write versão 2) oferece vantagens como compressão e suporte a snapshots. Vamos converter nossa imagem para qcow2:
1
qemu-img convert -O qcow2 -c build/os-base/debian-kvm-template.raw /home/gean/kvm/templates/debian-12-amd64.qcow2
Este comando utiliza a ferramenta qemu-img para converter a imagem:
-O qcow2
: Especifica o formato de saída como qcow2-c
: Ativa a compressão, reduzindo o tamanho do arquivo- O primeiro argumento é o caminho para a imagem de origem (formato raw)
- O segundo argumento é o caminho para a imagem de destino (formato qcow2)
Você pode ajustar o caminho de destino conforme sua preferência. Neste exemplo, estamos salvando a imagem em /home/gean/kvm/templates/, mas você pode escolher qualquer diretório que seja conveniente para seu ambiente.
Após a conversão, você terá uma imagem qcow2 comprimida pronta para uso em ambientes QEMU/KVM, como libvirt, OpenStack ou outras plataformas que suportem este formato.
Testando o Template com Terraform
Agora que temos nossa imagem pronta, vamos testá-la usando o Terraform. O Terraform nos permitirá provisionar uma VM usando nossa imagem e verificar se ela funciona conforme esperado.
Primeiro, vamos criar a estrutura de diretórios para nosso projeto Terraform:
1
2
mkdir -p ~/terraform/kvm
cd ~/terraform/kvm
Agora, vamos criar o arquivo principal de configuração do Terraform:
1
vim main.tf
Vamos analisar o conteúdo deste arquivo em detalhes:
1
2
3
4
5
6
7
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
}
}
}
Esta seção inicial define o provider libvirt, que é necessário para interagir com o QEMU/KVM através da API libvirt. O provider é mantido pela comunidade e está disponível no Terraform Registry.
1
2
3
provider "libvirt" {
uri = "qemu:///system"
}
Aqui, configuramos o provider libvirt para se conectar ao daemon libvirt local através do URI “qemu:///system”. Este URI é o padrão para conexões locais com privilégios de sistema.
1
2
3
4
5
6
7
8
9
10
11
12
resource "libvirt_network" "debpacker" {
name = "debpacker"
mode = "nat"
addresses = ["10.5.6.0/24"]
autostart = true
dhcp {
enabled = false
}
dns {
enabled = true
}
}
Esta seção cria uma rede virtual chamada “debpacker” para nossa VM. A rede utiliza o modo NAT, que permite que a VM acesse a internet através do host, e é configurada com o range de endereços 10.5.6.0/24. Desativamos o DHCP porque configuraremos o endereço IP estaticamente, e ativamos o DNS para resolução de nomes.
1
2
3
4
5
6
resource "libvirt_volume" "os_image" {
name = "debpacker.qcow2"
pool = "default"
source = "/home/gean/kvm/templates/debian-12-amd64.qcow2"
format = "qcow2"
}
Aqui, definimos o volume de disco para nossa VM. Especificamos o nome do volume, o pool de armazenamento (default), o caminho para nossa imagem qcow2 e o formato. O Terraform copiará a imagem para o pool de armazenamento libvirt.
1
2
3
4
5
6
7
data "template_file" "user_data" {
template = file("${path.module}/cloud_init.yml")
}
data "template_file" "network_config" {
template = file("${path.module}/network_cfg.yml")
}
Estas seções carregam os arquivos de configuração do cloud-init e da rede. O cloud-init é usado para configuração inicial da VM, como criação de usuários e execução de scripts, enquanto a configuração de rede define como a VM se conectará à rede.
1
2
3
4
5
6
resource "libvirt_cloudinit_disk" "cloudinit_debpacker" {
name = "cloudinit_debpacker.iso"
user_data = data.template_file.user_data.rendered
network_config = data.template_file.network_config.rendered
pool = "default"
}
Aqui, criamos um disco ISO contendo as configurações do cloud-init. Este disco será montado na VM durante o boot e usado pelo cloud-init para configurar o sistema.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "libvirt_domain" "debpacker" {
name = "debpacker"
memory = "2048"
vcpu = 2
cpu {
mode = "host-passthrough"
}
cloudinit = libvirt_cloudinit_disk.cloudinit_debpacker.id
network_interface {
network_name = libvirt_network.debpacker.name
}
console {
type = "pty"
target_port = "0"
target_type = "serial"
}
disk {
volume_id = libvirt_volume.os_image.id
}
graphics {
type = "spice"
listen_type = "none"
}
}
Esta seção final define a VM propriamente dita. Configuramos:
- Nome da VM como “debpacker”
- 2GB de memória RAM
- 2 vCPUs
- Modo de CPU como “host-passthrough” para melhor desempenho
- Disco cloud-init criado anteriormente
- Interface de rede conectada à rede “debpacker”
- Console serial para acesso via linha de comando
- Disco principal usando o volume criado anteriormente
- Interface gráfica usando o protocolo SPICE
Agora, precisamos criar os arquivos de configuração do cloud-init. Primeiro, o arquivo cloud_init.yml:
1
vim cloud_init.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#cloud-config
users:
- name: gean
gecos: "Gean Martins"
sudo: "ALL=(ALL) NOPASSWD:ALL"
shell: /bin/bash
lock_passwd: false
ssh-authorized-keys:
- ${file("~/.ssh/tfvms.pub")}
ssh_pwauth: true
chpasswd:
list: |
root:<USE mkpasswd --method=SHA-512 PARA CRIAR A SENHA>
expire: false
runcmd:
- hostnamectl set-hostname deb-packer
Este arquivo configura o cloud-init para:
- Criar um usuário chamado “gean” com acesso sudo sem senha
- Adicionar a chave SSH pública do arquivo ~/.ssh/tfvms.pub para autenticação sem senha
- Permitir autenticação por senha via SSH
- Definir uma senha para o usuário root (você deve substituir o placeholder por uma senha criptografada gerada com mkpasswd)
- Definir o hostname como “deb-packer”
Para gerar uma senha criptografada para o usuário root, você pode usar o comando mkpasswd:
1
2
sudo apt-get install -y whois
mkpasswd --method=SHA-512
O comando solicitará uma senha e retornará a versão criptografada, que você deve usar no lugar do placeholder no arquivo cloud_init.yml.
Em seguida, criamos o arquivo de configuração de rede:
1
vim network_cfg.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
#cloud-config
network:
version: 2
ethernets:
ens3:
addresses:
- 10.5.6.10/24
nameservers:
addresses:
- 10.5.6.1
routes:
- to: default
via: 10.5.6.1
Este arquivo configura a rede da VM para:
- Usar a interface ens3 (padrão para VMs QEMU/KVM)
- Configurar o endereço IP estático 10.5.6.10/24
- Usar 10.5.6.1 (gateway da rede NAT) como servidor DNS
- Configurar uma rota padrão através de 10.5.6.1
Com todos os arquivos criados, podemos inicializar o Terraform e aplicar a configuração:
1
2
3
4
5
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
Estes comandos:
- Inicializam o diretório de trabalho do Terraform e baixam o provider libvirt
- Formatam os arquivos de configuração para seguir o estilo padrão
- Validam a sintaxe da configuração
- Mostram um plano de execução, detalhando o que será criado
- Aplicam o plano, criando os recursos definidos
Após a execução bem-sucedida do terraform apply
, você terá uma VM em execução usando a imagem que criamos. Você pode se conectar a ela via SSH usando o endereço IP configurado:
1
ssh gean@10.5.6.10
Ou, se preferir, pode acessar o console da VM usando o virsh:
1
virsh console debpacker
Para destruir a VM quando não precisar mais dela, execute:
1
terraform destroy
Este comando removerá todos os recursos criados pelo Terraform, incluindo a VM, o disco e a rede.
Considerações Finais
Neste tutorial, exploramos o processo completo de criação de um template Debian 12 para QEMU/KVM usando o Packer. Começamos com a instalação das ferramentas necessárias (KVM/LIBVIRT, Terraform e Packer), criamos os arquivos de configuração do Packer, executamos o build da imagem, convertemos para o formato qcow2 e finalmente testamos a imagem usando o Terraform.
O template que criamos é uma base sólida para ambientes de virtualização e nuvem, com suporte a cloud-init para configuração dinâmica e personalização. Algumas possíveis personalizações e melhorias incluem:
- Adicionar pacotes específicos para seu caso de uso
- Configurar regras de firewall
- Implementar hardening de segurança
- Otimizar o sistema para diferentes cargas de trabalho
- Adicionar monitoramento e logging
Para ambientes de produção, recomendamos:
- Usar senhas fortes ou, preferencialmente, autenticação por chave SSH
- Remover usuários e senhas padrão antes de disponibilizar a imagem
- Manter o sistema atualizado com as últimas correções de segurança
- Documentar todas as personalizações feitas no template
- Implementar um processo de atualização regular do template
Com as ferramentas e técnicas apresentadas neste tutorial, você está bem equipado para criar e gerenciar templates de máquinas virtuais de forma eficiente e automatizada, seguindo as melhores práticas de infraestrutura como código.
Referências
- Documentação oficial do Packer
- Documentação do plugin QEMU para Packer
- Documentação do Debian sobre instalação automatizada
- Documentação do cloud-init
- Documentação do Terraform
- Documentação do provider libvirt para Terraform
- Documentação do KVM
- Documentação do LIBVIRT
- Guia de particionamento do Debian
- Documentação do QEMU