Post

Criação de Template Debian 12 QEMU/KVM com Packer

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á gerado
  • disk_size: Tamanho do disco virtual em MB (8GB neste caso)
  • iso_url: URL para download da ISO do Debian 12
  • iso_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 e disk_size: Utilizam as variáveis definidas anteriormente
  • memory: 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á salva
  • disk_interface e net_device: Utilizam drivers virtio para melhor desempenho
  • format: Formato da imagem de saída (raw)
  • headless: Se false, mostra a interface gráfica durante o build; se true, executa em modo headless
  • accelerator: 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 e ssh_password: Credenciais para acesso SSH
  • ssh_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:

  1. Um provisionador “file” que copia o script cleanup.sh para a VM
  2. 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 falhar
  • set -u: Trata variáveis não definidas como erro
  • set -o pipefail: Faz com que um pipeline retorne erro se qualquer comando falhar
  • IFS=$'\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 nuvem
  • netplan.io: Sistema moderno de configuração de rede para Ubuntu e Debian
  • systemd-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:

  1. Download da ISO do Debian (se ainda não estiver em cache)
  2. Criação de uma máquina virtual QEMU/KVM temporária
  3. Inicialização da VM com a ISO e execução do boot_command
  4. Instalação automatizada do Debian usando o arquivo preseed.cfg
  5. Espera pela disponibilidade do SSH
  6. Execução dos provisionadores (cópia e execução do script cleanup.sh)
  7. Desligamento da VM
  8. 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:

  1. Inicializam o diretório de trabalho do Terraform e baixam o provider libvirt
  2. Formatam os arquivos de configuração para seguir o estilo padrão
  3. Validam a sintaxe da configuração
  4. Mostram um plano de execução, detalhando o que será criado
  5. 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:

  1. Usar senhas fortes ou, preferencialmente, autenticação por chave SSH
  2. Remover usuários e senhas padrão antes de disponibilizar a imagem
  3. Manter o sistema atualizado com as últimas correções de segurança
  4. Documentar todas as personalizações feitas no template
  5. 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

This post is licensed under CC BY 4.0 by the author.