Post

Criação de Template Oracle Linux 9 QEMU/KVM com Packer

Criação de Template Oracle Linux 9 QEMU/KVM com Packer

Introdução

A criação de máquinas virtuais é uma tarefa fundamental 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 Oracle Linux, uma distribuição empresarial baseada no Red Hat Enterprise Linux (RHEL), oferece estabilidade, segurança e suporte de longo prazo, tornando-o uma escolha excelente para ambientes corporativos. A versão 9, em particular, traz melhorias significativas em termos de desempenho, segurança e compatibilidade com aplicações modernas. Combinar o Oracle Linux 9 com ferramentas de automação como o Packer permite criar uma infraestrutura robusta e facilmente gerenciável.

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 Oracle Linux 9 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

Configurando o KVM no Ubuntu

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 configurar esses componentes para criar um ambiente robusto para nossas máquinas virtuais.

Instalando os Pacotes Necessários

Primeiro, vamos instalar os pacotes essenciais para trabalhar com KVM e gerenciar máquinas virtuais através de uma interface gráfica (Virt-Manager) e linha de comando:

1
sudo apt install virt-manager libosinfo-bin mkisofs -y

Neste comando, estamos instalando:

  • virt-manager: Uma interface gráfica para gerenciar máquinas virtuais
  • libosinfo-bin: Biblioteca que fornece informações sobre sistemas operacionais para ferramentas de virtualização
  • mkisofs: Ferramenta para criar imagens ISO, útil para configurações personalizadas

Para permitir que seu usuário gerencie máquinas virtuais sem precisar de privilégios de root, adicione-o ao grupo kvm:

1
sudo usermod -aG kvm $USER

Esta alteração de grupo requer um reinício do sistema para ser aplicada corretamente:

1
sudo systemctl reboot

Criando Diretórios para Armazenamento

Uma boa organização dos arquivos de máquinas virtuais facilita o gerenciamento e a manutenção. Vamos criar uma estrutura de diretórios dedicada:

1
2
3
mkdir -p ~/kvm/images
mkdir -p ~/kvm/templates
mkdir -p ~/kvm/isos

Esta estrutura inclui:

  • images: Diretório principal onde as máquinas virtuais em uso serão armazenadas
  • templates: Diretório para armazenar imagens base pré-configuradas
  • isos: Diretório para armazenar arquivos ISO de instalação de sistemas operacionais

Configurando Pools de Armazenamento

O Libvirt utiliza o conceito de “pools de armazenamento” para gerenciar onde os discos virtuais são armazenados. Vamos criar três pools correspondentes aos diretórios que acabamos de criar:

1
2
3
virsh pool-define-as --name default --type dir --target ~/kvm/images
virsh pool-define-as --name templates --type dir --target ~/kvm/templates
virsh pool-define-as --name isos --type dir --target ~/kvm/isos

Cada comando cria um pool de armazenamento com um nome específico, do tipo diretório, apontando para o caminho que definimos anteriormente.

Para garantir que esses pools sejam iniciados automaticamente quando o sistema for ligado:

1
2
3
virsh pool-autostart default
virsh pool-autostart templates
virsh pool-autostart isos

E para iniciar os pools imediatamente, sem precisar reiniciar o sistema:

1
2
3
virsh pool-start default
virsh pool-start templates
virsh pool-start isos

Para verificar se os pools foram criados e iniciados corretamente:

1
2
virsh pool-list --all
virsh pool-info default

O primeiro comando lista todos os pools disponíveis, enquanto o segundo mostra informações detalhadas sobre o pool “default”.

Ajustando Permissões no AppArmor

O AppArmor é um sistema de segurança presente em distribuições como o Ubuntu que pode restringir o acesso a determinados recursos. Para evitar problemas de permissão ao acessar os diretórios de armazenamento das VMs, vamos criar um perfil personalizado:

1
2
3
4
5
6
7
8
9
sudo tee /etc/apparmor.d/libvirt/TEMPLATE.qemu > /dev/null <<EOF
#include <tunables/global>

profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
  #include <abstractions/libvirt-qemu>
  /home/$USER/kvm/images/** rwk,
  /home/$USER/kvm/templates/** rwk,
}
EOF

Este perfil permite que o QEMU (usado pelo KVM) acesse os diretórios que criamos com permissões de leitura, escrita e criação de links (rwk).

Agora, precisamos verificar qual usuário e grupo o Libvirt utiliza para executar as máquinas virtuais:

1
sudo grep -E '#user|#group' /etc/libvirt/qemu.conf

Se a saída mostrar algo como:

1
2
#user = "libvirt-qemu"
#group = "kvm"

Então, devemos ajustar as permissões dos diretórios para esse usuário e grupo:

1
sudo chown libvirt-qemu:kvm ~/kvm/ -R

Este comando altera o proprietário e o grupo de todos os arquivos e diretórios dentro de ~/kvm/ para libvirt-qemu:kvm, permitindo que o Libvirt acesse esses recursos.

Finalmente, recarregamos as regras do AppArmor e reiniciamos o serviço Libvirt:

1
2
sudo apparmor_parser -r /etc/apparmor.d/libvirt/TEMPLATE.qemu
sudo systemctl restart libvirtd

Para verificar se há erros relacionados ao AppArmor:

1
sudo journalctl -xe | grep apparmor

Se não houver mensagens de erro, a configuração foi bem-sucedida.

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, o que nos permitirá criar e gerenciar máquinas virtuais de forma automatizada.

Baixando e Instalando a Última Versão

Primeiro, vamos obter a versão mais recente do Terraform diretamente do repositório oficial:

1
TER_VER=$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | cut -d: -f2 | tr -d \"\,\v | awk '{$1=$1};1')

Este comando consulta a API do GitHub para obter a tag da versão mais recente do Terraform e a armazena na variável TER_VER.

Agora, vamos baixar o binário, descompactá-lo e instalá-lo:

1
2
3
4
wget https://releases.hashicorp.com/terraform/${TER_VER}/terraform_${TER_VER}_linux_amd64.zip
unzip terraform_${TER_VER}_linux_amd64.zip
sudo mv terraform /usr/local/bin/
rm terraform_${TER_VER}_linux_amd64.zip

Estes comandos:

  1. Baixam o arquivo ZIP da versão mais recente do Terraform
  2. Descompactam o arquivo
  3. Movem o binário para o diretório /usr/local/bin/ (que geralmente está no PATH)
  4. Removem o arquivo ZIP, que não é mais necessário

Para verificar se a instalação foi bem-sucedida:

1
2
which terraform
terraform --version

O primeiro comando deve mostrar o caminho para o binário do Terraform (/usr/local/bin/terraform), e o segundo deve exibir a versão instalada.

Configurando o Ambiente para o Provider Libvirt

O Terraform utiliza “providers” para interagir com diferentes plataformas. Para trabalhar com o KVM/Libvirt, precisamos configurar o ambiente adequadamente.

Primeiro, vamos definir a variável de ambiente que especifica o URI padrão para conexão com o Libvirt. Adicione a seguinte linha ao seu arquivo ~/.bashrc:

1
export LIBVIRT_DEFAULT_URI="qemu:///system"

Você pode fazer isso manualmente ou usando o seguinte comando:

1
2
echo 'export LIBVIRT_DEFAULT_URI="qemu:///system"' >> ~/.bashrc
source ~/.bashrc

Esta configuração indica ao Terraform que deve se conectar ao daemon Libvirt local 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. Vamos criar um par de chaves dedicado para este propósito:

1
ssh-keygen -t rsa -b 4096 -f ~/.ssh/tfvms

Este comando cria:

  • Uma chave privada em ~/.ssh/tfvms
  • Uma 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, permitindo que você se conecte a elas sem precisar digitar uma senha.

Instalação do Packer no Ubuntu

O Packer é a ferramenta central que utilizaremos para criar nossos templates de máquinas virtuais. Vamos instalá-lo a partir do repositório oficial da HashiCorp.

Primeiro, adicionamos a chave GPG do repositório:

1
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

Em seguida, adicionamos o repositório ao 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:

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 Packer. 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 (Oracle Linux 9):

1
2
mkdir -p ~/packer/kvm/ol9
cd ~/packer/kvm/ol9

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 ol9, criaremos todos os arquivos necessários para nosso projeto: o template principal do Packer, o script de limpeza e o arquivo kickstart para automação da instalação do Oracle Linux 9.

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 = "ol9-kvm-template.raw"
}

variable "disk_size" {
  default = "16384"
}

variable "iso_url" {
  default = "https://yum.oracle.com/ISOS/OracleLinux/OL9/u4/x86_64/OracleLinux-R9-U4-x86_64-boot.iso"
}

variable "iso_checksum" {
  default = "975de11be8761efa4aa2c87d7d3bedcb62c9dc956909b68f62c99062d11599e9"
}

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 (16GB neste caso, maior que o Debian devido às necessidades do Oracle Linux)
  • iso_url: URL para download da ISO do Oracle Linux 9 Update 4
  • 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 da Oracle.

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
source "qemu" "iso" {
  vm_name          = var.vm_name 
  iso_url          = var.iso_url
  iso_checksum     = var.iso_checksum
  disk_size        = var.disk_size
  memory           = 2048
  disk_image       = false
  output_directory = "build/os-base"
  accelerator      = "kvm"
  disk_interface   = "virtio"
  format           = "raw"
  net_device       = "virtio-net"
  boot_wait        = "3s"
  boot_command = [
    "<up>",
    "<tab><wait>",
    " inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/ks.cfg",
    "<enter>"
  ]
  http_directory   = "http"
  cpu_model        = "host"
  shutdown_command = "echo 'packer' | sudo -S shutdown -P now"
  ssh_username     = "packer"
  ssh_password     = "packer"
  ssh_timeout      = "60m"
}

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 (2GB, mais que o Debian para garantir uma instalação suave do Oracle Linux)
  • 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
  • accelerator: Utiliza KVM para aceleração de hardware
  • disk_interface e net_device: Utilizam drivers virtio para melhor desempenho
  • format: Formato da imagem de saída (raw)
  • boot_wait: Tempo de espera antes de enviar comandos de boot
  • boot_command: Sequência de comandos enviados durante o boot para iniciar a instalação automatizada

O boot_command é específico para o Oracle Linux. Primeiro, pressionamos a tecla “up” para selecionar a opção de instalação, depois “tab” para editar os parâmetros de boot, e finalmente adicionamos o parâmetro inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/ks.cfg que indica ao instalador para usar nosso arquivo kickstart.

O http_directory especifica o diretório onde o arquivo kickstart estará disponível. O Packer inicia um servidor HTTP temporário durante o build para disponibilizar este arquivo.

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 (definidas no arquivo kickstart)
  • ssh_timeout: Tempo máximo de espera pela disponibilidade do SSH (60 minutos, mais que o Debian devido ao tempo de instalação potencialmente maior do Oracle Linux)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
build {
  sources = ["source.qemu.iso"]

  # Envia o script de limpeza para a VM
  provisioner "file" {
    source      = "cleanup.sh"          
    destination = "/tmp/cleanup.sh"      
  }

  # Executa o script de limpeza na VM
  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
4
5
#!/usr/bin/env bash

# Atualização e instalação de pacotes
dnf update -y
dnf install -y cloud-init

O início do script atualiza todos os pacotes do sistema para as versões mais recentes e instala o cloud-init, que é essencial para a configuração inicial de instâncias em ambientes de nuvem. Diferente do Debian, o Oracle Linux utiliza o gerenciador de pacotes DNF, que é o sucessor do YUM.

1
2
3
4
5
6
# Configura o NoCloud como fonte de dados
cat <<EOF > /etc/cloud/cloud.cfg.d/99-nocloud-datasource.cfg
datasource_list:
- NoCloud
- None
EOF

Esta seção configura o cloud-init para usar o datasource NoCloud, que é adequado para ambientes de virtualização local como o KVM. O NoCloud permite que configurações sejam fornecidas através de um disco ISO ou um servidor web local, sem depender de um provedor de nuvem específico.

1
2
3
4
5
6
7
8
9
# Remoção de pacotes e dependências desnecessárias
dnf autoremove -y
dnf clean all

# Limpeza de cache, logs e arquivos temporários
rm -rf /var/cache/dnf
rm -rf /var/log/*
rm -rf /tmp/*
rm -rf /var/tmp/*

Aqui, removemos pacotes desnecessários e limpamos caches, logs e arquivos temporários para reduzir o tamanho da imagem final. O comando dnf autoremove remove pacotes que foram instalados como dependências mas não são mais necessários, enquanto dnf clean all limpa os caches do gerenciador de pacotes.

1
2
3
4
# Remoção de arquivos específicos da instância
truncate -s 0 /etc/machine-id
rm -f /etc/ssh/*key*
rm -f ~/.bash_history

Esta seção remove arquivos específicos da instância que não devem ser compartilhados entre múltiplas VMs:

  • O machine-id é zerado para que cada nova instância receba um ID único
  • As chaves SSH são removidas para que novas chaves sejam geradas em cada instância
  • O histórico de comandos bash é removido para limpar rastros de configuração
1
2
# Zerar espaço livre
fstrim -av

O comando final, fstrim -av, libera blocos de armazenamento não utilizados, o que pode reduzir significativamente o tamanho da imagem final quando ela for comprimida. A opção -a aplica o comando a todos os sistemas de arquivos montados, e -v ativa o modo verboso para mostrar o progresso.

Arquivo Kickstart

O arquivo kickstart é fundamental para automatizar a instalação do Oracle Linux, permitindo que todo o processo ocorra sem intervenção manual. Vamos criar o diretório http e o arquivo ks.cfg:

1
2
mkdir http
vim http/ks.cfg

Agora, vamos analisar o conteúdo deste arquivo em detalhes:

graphical

Esta primeira linha indica que a instalação deve usar o modo gráfico. Embora não vejamos a interface gráfica durante o build do Packer (que geralmente é headless), esta configuração garante que todos os pacotes necessários para um ambiente gráfico sejam instalados.

%addon com_redhat_kdump --enable --reserve-mb='auto'
%end

Esta seção configura o kdump, uma funcionalidade que permite capturar dumps de kernel em caso de falhas, útil para diagnóstico de problemas. A opção --reserve-mb='auto' configura automaticamente a quantidade de memória reservada para o kdump.

keyboard --xlayouts='br'
lang pt_BR.UTF-8

Aqui, configuramos o layout de teclado como brasileiro (br) e o idioma como português do Brasil (pt_BR.UTF-8).

url --url="http://yum.oracle.com/repo/OracleLinux/OL9/baseos/latest/x86_64"

Esta linha especifica o repositório a ser usado durante a instalação. Estamos usando o repositório oficial da Oracle para o Oracle Linux 9, especificamente o repositório BaseOS para arquitetura x86_64.

%packages
@^minimal-environment
%end

Na seção de pacotes, selecionamos o grupo “minimal-environment”, que instala apenas os pacotes essenciais para um sistema funcional. Isso resulta em uma imagem menor e mais rápida de criar.

firstboot --enable

Esta opção habilita o assistente de primeiro boot, que será executado na primeira inicialização do sistema. No entanto, como estamos usando cloud-init, muitas das configurações que normalmente seriam feitas nesse assistente serão automatizadas.

ignoredisk --only-use=vda
autopart
clearpart --none --initlabel

Estas linhas configuram o particionamento:

  • ignoredisk --only-use=vda: Usa apenas o disco vda (o primeiro disco virtual em ambientes KVM)
  • autopart: Utiliza o particionamento automático
  • clearpart --none --initlabel: Não remove partições existentes, apenas inicializa o rótulo do disco
timezone America/Sao_Paulo --utc

Configura o fuso horário para America/Sao_Paulo (horário de Brasília) e define que o relógio do hardware deve usar UTC.

rootpw --lock
user --groups=wheel --name=packer --password=$6$Jfl.H/kwDSeu7FWF$S/Mc/qsxM2DRupVkxKQawCgjQ3.i6beyCgY1fQCC0NUadW9pucgYnraGMWaCSg8g6t4GbozowE40X/FtAGert. --iscrypted --gecos="packer"

Estas linhas configuram os usuários:

  • rootpw --lock: Bloqueia a conta root, uma prática recomendada de segurança
  • user --groups=wheel --name=packer...: Cria um usuário chamado “packer” com acesso ao grupo wheel (que permite usar sudo)
  • --password=...: Define uma senha criptografada para o usuário
  • --iscrypted: Indica que a senha fornecida já está criptografada
  • --gecos="packer": Define informações adicionais sobre o usuário
reboot

Esta opção faz com que o sistema reinicie automaticamente após a conclusão da instalação.

%post
echo "packer   ALL=(ALL)   NOPASSWD: ALL" > /etc/sudoers.d/packer
/bin/chown root:root /etc/sudoers.d/packer
/bin/chmod 0440 /etc/sudoers.d/packer
%end

A seção %post contém comandos que são executados após a instalação do sistema, mas antes do primeiro boot. Aqui, estamos:

  1. Criando um arquivo em /etc/sudoers.d/ que permite ao usuário “packer” executar comandos sudo sem senha
  2. Definindo o proprietário e o grupo do arquivo como root
  3. Configurando as permissões corretas (0440) para o arquivo

Esta configuração é necessária para que o Packer possa 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 Oracle Linux (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 Oracle Linux usando o arquivo kickstart
  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/ol9-kvm-template.raw /home/gean/kvm/templates/ol9-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" "olpacker" {
  name      = "olpacker"
  mode      = "nat"
  addresses = ["10.3.4.0/24"]
  autostart = true
  dhcp {
    enabled = false
  }
  dns {
    enabled = true
  }
}

Esta seção cria uma rede virtual chamada “olpacker” 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.3.4.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   = "olpacker.qcow2"
  pool   = "default"
  source = "/home/gean/kvm/templates/ol9-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_olpacker" {
  name           = "cloudinit_olpacker.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" "olpacker" {
  name   = "olpacker"
  memory = "2048"
  vcpu   = 2

  cpu {
    mode = "host-passthrough"
  }

  cloudinit = libvirt_cloudinit_disk.cloudinit_olpacker.id

  network_interface {
    network_name = libvirt_network.olpacker.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 “olpacker”
  • 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 “olpacker”
  • 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 olpacker 

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 “olpacker”

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
#cloud-config
network:
  version: 1
  config:
    - type: physical
      name: ens3
      subnets:
        - type: static
          address: 10.3.4.12/24
          gateway: 10.3.4.1
          dns_nameservers: 10.3.4.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.3.4.12/24
  • Usar 10.3.4.1 (gateway da rede NAT) como servidor DNS e gateway

Note que a sintaxe da configuração de rede é diferente da usada no Debian. O Oracle Linux 9, sendo baseado no RHEL 9, utiliza o formato de configuração de rede versão 1, enquanto o Debian utiliza a versão 2.

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.3.4.12

Ou, se preferir, pode acessar o console da VM usando o virsh:

1
virsh console olpacker

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 Oracle Linux 9 para QEMU/KVM usando o Packer. Começamos com a configuração do ambiente KVM/Libvirt, instalação do Terraform e Packer, criação dos arquivos de configuração, execução do build da imagem, conversão para o formato qcow2 e finalmente teste da 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 com firewalld (o firewall padrão do Oracle Linux)
  • Implementar hardening de segurança seguindo as diretrizes da Oracle
  • 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

O Oracle Linux 9 oferece várias vantagens para ambientes empresariais, incluindo:

  • Compatibilidade com aplicações certificadas para Red Hat Enterprise Linux
  • Acesso ao repositório Oracle UEK (Unbreakable Enterprise Kernel) para melhor desempenho
  • Suporte de longo prazo da Oracle
  • Integrações otimizadas com outros produtos Oracle

Com as ferramentas e técnicas apresentadas neste tutorial, você está bem equipado para criar e gerenciar templates de máquinas virtuais Oracle Linux 9 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.