Este guia detalha o processo de criação automatizada de imagens do sistema operacional Oracle Linux 9 utilizando o Packer e o QEMU. O Oracle Linux 9 é uma excelente alternativa ao CentOS/RHEL para ambientes corporativos e de homelab, especialmente por fornecer o Unbreakable Enterprise Kernel (UEK) otimizado para performance e estabilidade.
As imagens geradas servirão como templates para a criação de infraestrutura KVM/libvirt via Terraform, complementando o ambiente já estruturado.
O Packer já deve ter sido instalado no passo anterior. Caso não tenha feito, consulte o Guia de Criação de Imagem QEMU com Packer: Debian 13 (Trixie).
O projeto segue uma estrutura modular, separando configurações de hardware, variáveis, scripts de otimização e automação de instalação (Kickstart).
.
├── http
│ └── ks.cfg # Arquivo Kickstart para automação da instalação do Oracle Linux
├── scripts
│ └── customizing-image.sh # Script bash para limpeza profunda e otimização pós-instalação
├── oraclelinux-template.pkr.hcl# Definição principal da VM e comandos de boot
├── packer.pkr.hcl # Declaração de plugins necessários (QEMU)
├── provisioners.pkr.hcl # Etapas de provisionamento (cópia e execução de scripts)
├── secrets.pkrvars.hcl # Variáveis sensíveis (senhas) - NÃO comitar no Git
└── variables.pkr.hcl # Declaração e valores padrão de todas as variáveis
variables.pkr.hcl)Este arquivo centraliza as configurações. Foram adicionadas descrições detalhadas para compreender o impacto de cada parâmetro no build.
# variables.pkr.hcl
#############################################
# Configuração de ISO e Repositórios
#############################################
# Para outras versões, consulte: https://yum.oracle.com/oracle-linux-isos.html
variable "iso_url" {
type = string
description = "URL direta para download da imagem ISO (Boot ISO) do Oracle Linux 9 com UEK."
default = "https://yum.oracle.com/ISOS/OracleLinux/OL9/u7/x86_64/OracleLinux-R9-U7-x86_64-boot-uek.iso"
}
variable "iso_checksum" {
type = string
description = "Hash SHA256 para validar a integridade da ISO. Consulte: https://linux.oracle.com/security/gpg/"
default = "4aa32c09c14c5ab42652c1c16187900294c5f8acbaefb44e773ae405b5b5c3fa"
}
variable "kickstart_file" {
type = string
description = "Nome do arquivo Kickstart servido via HTTP para automatizar a instalação do SO."
default = "ks.cfg"
}
variable "repo_url" {
type = string
description = "URL do repositório BaseOS. O instalador buscará os pacotes diretamente da internet em vez de uma ISO completa."
default = "http://yum.oracle.com/repo/OracleLinux/OL9/baseos/latest/x86_64"
}
#############################################
# Hardware da VM de Build
#############################################
variable "cpus" {
type = number
description = "Número de núcleos de CPU alocados temporariamente para acelerar o processo de instalação."
default = 2
}
variable "memory" {
type = number
description = "Quantidade de memória RAM (em MB) alocada para a VM durante o build."
default = 2048
}
variable "disk_size" {
type = number
description = "Tamanho máximo do disco virtual (em MB). O formato qcow2 ocupará apenas o espaço real utilizado."
default = 16384
}
variable "qemu_binary" {
type = string
description = "Nome do executável do QEMU no sistema host."
default = "qemu-system-x86_64"
}
variable "vm_name" {
type = string
description = "Prefixo usado para nomear o diretório de saída e o arquivo final da imagem qcow2."
default = "ol9"
}
variable "headless" {
type = bool
description = "Se 'true', oculta a interface gráfica do QEMU. Ideal para servidores CI/CD."
default = false
}
#############################################
# Rede e Comunicação
#############################################
variable "communicator" {
type = string
description = "Protocolo usado pelo Packer para conectar na VM após a instalação."
default = "ssh"
}
variable "host_ports" {
type = string
description = "Intervalo de portas no host usadas para mapear o acesso SSH para dentro da VM."
default = "2222-4444"
}
variable "http_ports" {
type = string
description = "Intervalo de portas usadas pelo servidor HTTP interno do Packer (para servir o ks.cfg)."
default = "8000-9000"
}
variable "vnc_vrdp_bind_address" {
type = string
description = "Endereço IP no qual o servidor VNC do QEMU irá escutar."
default = "127.0.0.1"
}
variable "vnc_vrdp_ports" {
type = string
description = "Intervalo de portas para o acesso VNC."
default = "5900-6000"
}
#############################################
# Acesso SSH
#############################################
variable "ssh_password" {
type = string
sensitive = true
description = "Senha do usuário temporário para login SSH e sudo. Deve ser definida em secrets.pkrvars.hcl."
}
variable "ssh_username" {
type = string
description = "Nome do usuário criado durante a instalação via Kickstart."
default = "packer"
}
variable "ssh_fullname" {
type = string
description = "Nome completo do usuário temporário."
default = "Packer Build User"
}
variable "ssh_timeout" {
type = string
description = "Tempo máximo que o Packer aguardará o SSH ficar disponível."
default = "60m"
}
#############################################
# Localização e Sistema
#############################################
variable "language" {
type = string
description = "Idioma base do sistema operacional."
default = "pt_BR"
}
variable "keyboard" {
type = string
description = "Layout do teclado."
default = "br-abnt2"
}
variable "timezone" {
type = string
description = "Fuso horário do sistema."
default = "America/Sao_Paulo"
}
#############################################
# Firmware UEFI (OVMF)
#############################################
variable "ovmf_code" {
type = string
description = "Caminho para o firmware OVMF_CODE (UEFI). Essencial para boot moderno."
default = "/usr/share/OVMF/OVMF_CODE_4M.fd"
}
variable "ovmf_vars" {
type = string
description = "Caminho para a NVRAM UEFI."
default = "/usr/share/OVMF/OVMF_VARS_4M.fd"
}
# Lógica condicional para lidar com diferentes distribuições Linux no host
locals {
ovmf_code_fallback = fileexists(var.ovmf_code) ? var.ovmf_code : "/usr/share/edk2/ovmf/OVMF_CODE.fd"
ovmf_vars_fallback = fileexists(var.ovmf_vars) ? var.ovmf_vars : "/usr/share/edk2/ovmf/OVMF_VARS.fd"
output_directory = "build/${var.vm_name}-${formatdate("YYYY-MM-DD", timestamp())}"
}
secrets.pkrvars.hcl)Este arquivo contém a senha do usuário packer. Lembre-se de adicioná-lo ao .gitignore.
# secrets.pkrvars.hcl
# =============================================================================
# ARQUIVO SENSÍVEL - Adicione ao .gitignore!
# =============================================================================
# Senha em texto plano usada para a instalação e acesso temporário
ssh_password = "packer_password_segura"
packer.pkr.hcl)# packer.pkr.hcl
packer {
required_version = ">= 1.7.0, < 2.0.0"
required_plugins {
qemu = {
source = "github.com/hashicorp/qemu"
version = ">= 1.0.0, < 2.0.0"
}
}
}
oraclelinux-template.pkr.hcl)Este arquivo configura o QEMU e injeta os comandos de boot para iniciar o instalador Anaconda via Kickstart.
# oraclelinux-template.pkr.hcl
source "qemu" "oraclelinux" {
# Sequência de teclas enviadas ao GRUB do instalador da ISO
# Interrompe o boot normal, edita a linha do kernel e injeta a URL do Kickstart
boot_command = [
"e", # Edita a entrada atual do GRUB
"<down><down>", # Desce até a linha do kernel (linuxefi)
"<leftCtrlOn>e<leftCtrlOff>", # Vai para o final da linha
"<spacebar>", "inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg", # Adiciona o parâmetro do Kickstart
"<leftCtrlOn>x<leftCtrlOff>" # Pressiona Ctrl+X para fazer o boot
]
boot_wait = var.boot_wait
communicator = var.communicator
cpus = var.cpus
accelerator = "kvm" # Aceleração de hardware
# Configurações de Disco
disk_cache = "writeback"
disk_compression = true # Habilita compressão qcow2 nativa
disk_discard = "unmap" # Suporte a TRIM
disk_interface = "virtio-scsi" # Controladora SCSI paravirtualizada (alta performance)
disk_size = var.disk_size
format = "qcow2"
# Servidor HTTP interno do Packer
http_content = { "/${var.kickstart_file}" = templatefile("http/${var.kickstart_file}", var) }
http_port_min = split("-", var.http_ports)[0]
http_port_max = split("-", var.http_ports)[1]
iso_checksum = var.iso_checksum
iso_url = var.iso_url
# Sistema e Boot
cpu_model = "host" # Expõe as flags reais da CPU do host para a VM
machine_type = "q35" # Chipset moderno PCIe
efi_boot = true # Força boot UEFI
efi_firmware_code = local.ovmf_code_fallback
efi_firmware_vars = local.ovmf_vars_fallback
memory = var.memory
net_device = "virtio-net" # Placa de rede virtio
output_directory = local.output_directory
qemu_binary = var.qemu_binary
# Desligamento seguro
shutdown_command = "echo '${var.ssh_password}' | sudo -S poweroff"
shutdown_timeout = var.shutdown_timeout
# Credenciais SSH
ssh_password = var.ssh_password
ssh_username = var.ssh_username
ssh_timeout = var.ssh_timeout
# Interface
headless = var.headless
use_default_display = false
vnc_bind_address = var.vnc_vrdp_bind_address
}
provisioners.pkr.hcl)# provisioners.pkr.hcl
build {
sources = ["source.qemu.oraclelinux"]
# Copia o script bash para o diretório /tmp da VM
provisioner "file" {
source = "scripts/customizing-image.sh"
destination = "/tmp/customizing-image.sh"
}
# Dá permissão de execução e roda o script como root
provisioner "shell" {
inline = [
"chmod +x /tmp/customizing-image.sh",
"echo '${var.ssh_password}' | sudo -S /tmp/customizing-image.sh"
]
}
}
O arquivo ks.cfg instrui o instalador do RedHat/Oracle Linux (Anaconda) sobre como configurar o sistema.
# http/ks.cfg
# Configurações básicas (Texto, Teclado, Idioma)
text
keyboard ${keyboard}
lang ${language}.UTF-8
# Instalação via rede (Network Install)
url --url="${repo_url}"
# Configuração de repositórios adicionais (UEK R8 e AppStream)
repo --name="ol9_UEKR8" --baseurl="http://yum.oracle.com/repo/OracleLinux/OL9/UEKR8/x86_64/"
repo --name="ol9_appstream" --baseurl="http://yum.oracle.com/repo/OracleLinux/OL9/appstream/x86_64/"
# Seleção de Pacotes
%packages
@minimal-environment # Instalação mínima sem interface gráfica
kernel-uek-core # Força a instalação do Unbreakable Enterprise Kernel (UEK)
kernel-uek-modules-core
-kernel # Exclui o kernel padrão (RHCK) para economizar espaço
-kernel-core
-kernel-modules
-kernel-modules-core
%end
firstboot --enable
## --- Receita de disco automatica
#ignoredisk --only-use=sda
#autopart
#clearpart --none --initlabel
## --- Receita de disco sem LVM
#ignoredisk --only-use=sda
#clearpart --all --initlabel --drives=sda
#part /boot/efi --fstype=efi --size=256 --ondisk=sda
#part /boot --fstype=ext4 --size=1024 --ondisk=sda
#part / --fstype=xfs --size=1 --grow --ondisk=sda
## --- Receita de disco com LVM
ignoredisk --only-use=sda
clearpart --all --initlabel --drives=sda
part /boot/efi --fstype=efi --size=512 --ondisk=sda
part /boot --fstype=xfs --size=1024 --ondisk=sda
part pv.01 --fstype=lvmpv --size=1 --grow --ondisk=sda
volgroup ol --pesize=4096 pv.01
logvol / --fstype=xfs --name=root --vgname=ol --size=1 --grow
## --- Receita de multiplas partições de disco com LVM
#ignoredisk --only-use=sda
#clearpart --all --initlabel --drives=sda
#part /boot/efi --fstype=efi --size=512 --ondisk=sda
#part /boot --fstype=xfs --size=1024 --ondisk=sda
#part pv.01 --fstype=lvmpv --size=1 --grow --ondisk=sda --grow
#volgroup ol --pesize=4096 pv.01
#logvol / --fstype=xfs --size=4096 --name=root --vgname=ol
#logvol /tmp --fstype=xfs --size=1024 --name=tmp --vgname=ol --fsoptions="nosuid,noexec"
#logvol /var --fstype=xfs --size=4096 --name=var --vgname=ol
#logvol /opt --fstype=xfs --size=1024 --name=opt --vgname=ol
#logvol /home --fstype=xfs --size=1024 --name=home --vgname=ol
timezone ${timezone} --utc
# Configuração de Usuários
rootpw --lock # Bloqueia o login direto como root
user --groups=wheel --name=${ssh_username} --password=${ssh_password} --gecos="${ssh_fullname}"
# Script Pós-Instalação (Executado no ambiente chroot)
%post
# 1. Recriar repositórios padrão do Oracle Linux apontando para mirrors OCI
rm -f /etc/yum.repos.d/*
cat > /etc/yum.repos.d/oracle-linux-ol9.repo << 'EOF'
[ol9_baseos_latest]
name=Oracle Linux 9 BaseOS Latest ($basearch)
baseurl=https://yum$ociregion.$ocidomain/repo/OracleLinux/OL9/baseos/latest/$basearch/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
gpgcheck=1
enabled=1
[ol9_appstream]
name=Oracle Linux 9 Application Stream Packages ($basearch)
baseurl=https://yum$ociregion.$ocidomain/repo/OracleLinux/OL9/appstream/$basearch/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
gpgcheck=1
enabled=1
EOF
# 2. Limpeza e Instalação de Agentes Cloud
dnf clean all
dnf remove -y linux-firmware microcode_ctl glibc-gconv-extra --setopt=clean_requirements_on_remove=1
dnf install -y linux-firmware-core linux-firmware-whence --setopt=install_weak_deps=False
dnf -y upgrade
# Instala cloud-init (para Terraform) e qemu-guest-agent (para comunicação KVM)
dnf install -y --setopt=install_weak_deps=False cloud-init qemu-guest-agent
%end
# Configuração do Kdump (Crash dumps)
%addon com_redhat_kdump --enable --reserve-mb='auto'
%end
reboot
O script customizing-image.sh remove artefatos únicos (machine-id, chaves SSH) e preenche o disco com zeros para maximizar a compressão.
#!/bin/bash
# scripts/customizing-image.sh
# Script de Otimização para Oracle Linux 9
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
log_info() { echo -e "\033[0;32m[INFO]\033[0m $1"; }
log_warn() { echo -e "\033[0;33m[WARN]\033[0m $1" >&2; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1" >&2; }
if [ "$EUID" -ne 0 ]; then
log_error "Execute como root"
exit 1
fi
log_info "Iniciando ${SCRIPT_NAME}..."
# -----------------------------------------------------------------------------
# 1. GRUB – Configuração de Console Serial
# -----------------------------------------------------------------------------
# Configura o GRUB para enviar a saída de boot para a porta serial (ttyS0).
# Isso permite acessar a VM via "virsh console" mesmo sem interface gráfica.
log_info "Configurando GRUB..."
cat > /etc/default/grub <<'EOF'
GRUB_TIMEOUT=1
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8 no_timer_check biosdevname=0 net.ifnames=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
EOF
grub2-mkconfig -o /boot/grub2/grub.cfg
# -----------------------------------------------------------------------------
# 2. LIMPEZA DE SISTEMA DE ARQUIVOS
# -----------------------------------------------------------------------------
log_info "Limpeza profunda de arquivos..."
# Remove manuais para economizar espaço
rm -rf /usr/share/doc/* /usr/share/info/* /usr/share/man/*
# Remove locales não utilizados (Economiza ~100MB)
log_info "Limpando locales desnecessários..."
find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name 'en_US*' ! -name 'C*' ! -name 'pt_BR*' -exec rm -rf {} + 2>/dev/null || true
# Limpa caches do gerenciador de pacotes DNF
dnf clean all
rm -rf /var/cache/dnf/* /var/lib/dnf/history*
# Esvazia os arquivos de log
find /var/log -type f -exec truncate -s 0 {} \;
# Para o kdump temporariamente para liberar arquivos em /var/tmp
systemctl stop kdump 2>/dev/null || true
# Limpa diretórios temporários
find /var/tmp -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
find /tmp -mindepth 1 -maxdepth 1 ! -name 'cleanup-image.sh' -exec rm -rf {} + 2>/dev/null || true
# -----------------------------------------------------------------------------
# 3. GENERALIZAÇÃO DA IMAGEM (Sysprep)
# -----------------------------------------------------------------------------
# Remove identificadores únicos para evitar conflitos quando a imagem for clonada.
log_info "Higiene final da imagem..."
truncate -s 0 /etc/machine-id
truncate -s 0 /etc/hostname
echo "localhost" > /etc/hostname
> /etc/resolv.conf
# Apaga chaves SSH do host (serão geradas automaticamente no primeiro boot)
rm -f /etc/ssh/ssh_host_*
# Reseta o estado do cloud-init
cloud-init clean --logs --seed 2>/dev/null || true
# -----------------------------------------------------------------------------
# 4. COMPACTAÇÃO (ZERO FILL)
# -----------------------------------------------------------------------------
# Escreve zeros em todo o espaço livre do disco. O Packer usará a opção "disk_discard=unmap"
# e compressão qcow2 para transformar esses zeros em espaço não alocado no arquivo final.
log_info "Zerofill para compactação de disco (isso pode demorar)..."
sync
dd if=/dev/zero of=/zero.fill bs=1M status=progress 2>/dev/null || true
sync
rm -f /zero.fill
fstrim -av 2>/dev/null || true
rm -f "$(readlink -f "$0")"
log_info "Otimização concluída!"
Inicialize, formate e valide o projeto:
packer init .
packer fmt .
packer validate .
Inicie o processo de build (lembre-se de criar o arquivo secrets.pkrvars.hcl antes):
PACKER_LOG=1 packer build -var-file=secrets.pkrvars.hcl .
Após a conclusão, o arquivo gerado estará na pasta configurada (ex: build/ol9-2026-04-03/ol9).
Copie para o servidor KVM:
scp build/ol9-2026-04-03/ol9 gean@192.168.0.251:
Acesse o servidor KVM e mova a imagem para o diretório de templates:
sudo mv ol9 /datastore/templates/ol9-base.qcow2
Verifique o tamanho incrivelmente otimizado da imagem final:
qemu-img info /datastore/templates/ol9-base.qcow2
Saída esperada:
image: /datastore/templates/ol9-base.qcow2
file format: qcow2
virtual size: 16 GiB (17179869184 bytes)
disk size: 524 MiB <-- Tamanho real extremamente reduzido
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
...
A imagem do Oracle Linux 9 está pronta e otimizada para uso em seus projetos com Terraform!