Post

Criação de Template Ubuntu 24.04 QEMU/KVM com Packer

Criação de Template Ubuntu 24.04 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 Ubuntu, uma das distribuições Linux mais populares do mundo, é conhecido por sua facilidade de uso, ampla comunidade de suporte e atualizações regulares. A versão 24.04 LTS (Noble Numbat), lançada em abril de 2024, traz melhorias significativas em termos de desempenho, segurança e suporte a hardware moderno, além de garantir suporte de longo prazo por cinco anos. Essas características tornam o Ubuntu 24.04 uma excelente escolha para ambientes de produção que necessitam de estabilidade e segurança.

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 Ubuntu 24.04 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 host, 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 no 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 “noble” para Ubuntu 24.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 (Ubuntu 24.04):

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

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 ubu24, criaremos todos os arquivos necessários para nosso projeto: o template principal do Packer, o script de limpeza e os arquivos de configuração do cloud-init para automação da instalação do Ubuntu 24.04.

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
16
17
18
19
20
21
22
23
24
25
source "qemu" "iso" {
  vm_name              = "ubuntu-24-amd64.raw"
  iso_url              = "https://www.releases.ubuntu.com/24.04/ubuntu-24.04.1-live-server-amd64.iso"
  iso_checksum         = "e240e4b801f7bb68c20d1356b60968ad0c33a41d00d828e74ceb3364a0317be9"
  memory               = 2048
  disk_image           = false
  output_directory     = "build/os-base"
  accelerator          = "kvm"
  disk_size            = "8192M"
  disk_interface       = "virtio"
  format               = "raw"
  net_device           = "virtio-net"
  boot_wait            = "3s"
  boot_command         = [
    "e<wait>",
    "<down><down><down><end>",
    " autoinstall ds=\"nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/\" ",
    "<f10>"
    ]
  http_directory       = "http"
  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: Nome do arquivo de imagem que será gerado
  • iso_url: URL para download da ISO do Ubuntu 24.04.1 Server
  • iso_checksum: Checksum SHA256 da ISO para verificação de integridade
  • memory: Define a quantidade de memória RAM para a VM (2GB)
  • 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_size: Tamanho do disco virtual (8GB)
  • 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 Ubuntu 24.04. A sequência de comandos:

  1. Pressiona “e” para editar as opções de boot do GRUB
  2. Navega até o final da linha de kernel (três vezes “down” e depois “end”)
  3. Adiciona o parâmetro autoinstall ds="nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/" que indica ao instalador para usar o método de autoinstalação com os arquivos de configuração disponibilizados pelo servidor HTTP do Packer
  4. Pressiona F10 para iniciar o boot com as opções modificadas

O http_directory especifica o diretório onde os arquivos de configuração do cloud-init estarão disponíveis. O Packer inicia um servidor HTTP temporário durante o build para disponibilizar estes arquivos.

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 user-data)
  • ssh_timeout: Tempo máximo de espera pela disponibilidade do SSH (60 minutos)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
build {
  name    = "iso"
  sources = ["source.qemu.iso"]

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

  # Executa o script dentro da 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

# Limpeza de pacotes e cache
DEBIAN_FRONTEND=noninteractive apt-get autoremove --yes --purge
DEBIAN_FRONTEND=noninteractive apt-get clean

O início do script configura a limpeza de pacotes e cache. A variável DEBIAN_FRONTEND=noninteractive garante que a execução ocorra sem interação do usuário, essencial para automação. O comando apt-get autoremove remove pacotes que foram instalados como dependências mas não são mais necessários, enquanto apt-get clean limpa os caches do gerenciador de pacotes.

1
2
# Limpeza de logs e arquivos temporários
find /var/cache/apt /var/lib/apt /var/log -type f -delete

Esta linha remove arquivos de cache do APT e logs do sistema, reduzindo o tamanho da imagem final. O comando find localiza todos os arquivos regulares (não diretórios) nos caminhos especificados e os exclui.

1
2
3
# Remover informações específicas da instância
truncate -s 0 /etc/machine-id
rm -f /etc/ssh/*key*

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
1
2
# Executa o trim nas partições montadas
fstrim -av

O comando 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.

1
2
# Limpeza do cloud-init para a próxima instância
cloud-init clean

O comando final, cloud-init clean, limpa o estado do cloud-init para garantir que ele seja executado corretamente na próxima inicialização da VM. Isso é importante para que cada nova instância criada a partir do template passe pelo processo de inicialização do cloud-init corretamente.

Arquivo de Configuração Cloud-Init

O Ubuntu 24.04 utiliza o sistema de autoinstalação baseado em cloud-init, que permite uma instalação totalmente automatizada. Vamos criar os arquivos necessários no diretório http:

1
2
3
mkdir http
touch http/meta-data
vim http/user-data

O arquivo meta-data pode ficar vazio, pois não precisamos de metadados específicos para nossa instalação. O arquivo user-data, por outro lado, contém todas as configurações para a instalação automatizada:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#cloud-config
autoinstall:
  apt:
    disable_components: []
    fallback: abort
    geoip: true
    mirror-selection:
      primary:
      - country-mirror
      - arches: &id001
        - amd64
        - i386
        uri: http://archive.ubuntu.com/ubuntu/
      - arches: &id002
        - s390x
        - arm64
        - armhf
        - powerpc
        - ppc64el
        - riscv64
        uri: http://ports.ubuntu.com/ubuntu-ports
    preserve_sources_list: false
    security:
    - arches: *id001
      uri: http://security.ubuntu.com/ubuntu/
    - arches: *id002
      uri: http://ports.ubuntu.com/ubuntu-ports
  codecs:
    install: false
  drivers:
    install: false
  identity:
    hostname: ubuntu
    password: $6$dRt6UXQCoPQXJc8L$CTqMc/fdh9y5KvWd8JimQEgLC57Gv7A/RMpzD9bb6XSwsQ61s6WMmFHdAg3fctJU/iOTUNHLNBucq.5Irj7wS0 
    realname: packer
    username: packer
  kernel:
    package: linux-generic
  keyboard:
    layout: br
    toggle: null
    variant: ''
  locale: pt_BR.UTF-8
  source:
    id: ubuntu-server-minimal
    search_drivers: false
  ssh:
    allow-pw: true
    authorized-keys: []
    install-server: true
  storage:
    layout:
      name: direct
  updates: security
  version: 1

Vamos analisar as principais seções deste arquivo:

  • apt: Configura os repositórios APT, incluindo mirrors primários e de segurança para diferentes arquiteturas. A opção geoip: true permite que o instalador selecione automaticamente o mirror mais próximo com base na localização geográfica.

  • codecs e drivers: Ambos estão configurados para não instalar codecs multimídia ou drivers adicionais durante a instalação, mantendo a imagem mais enxuta.

  • identity: Define as informações do usuário principal:
    • hostname: Nome da máquina (ubuntu)
    • password: Senha criptografada para o usuário (a string começando com $6$ é uma senha criptografada com SHA-512)
    • realname: Nome completo do usuário (packer)
    • username: Nome de login do usuário (packer)
  • kernel: Especifica o pacote de kernel a ser instalado (linux-generic).

  • keyboard: Configura o layout de teclado como brasileiro (br).

  • locale: Define o idioma e a localização como português do Brasil (pt_BR.UTF-8).

  • source: Especifica a variante do Ubuntu a ser instalada. A opção ubuntu-server-minimal instala uma versão mínima do Ubuntu Server, sem pacotes adicionais.

  • ssh: Configura o servidor SSH:
    • allow-pw: true: Permite autenticação por senha
    • install-server: true: Instala o servidor SSH
  • storage: Define o layout de armazenamento. A opção direct utiliza o particionamento mais simples, com uma única partição para o sistema.

  • updates: Configura para instalar apenas atualizações de segurança durante a instalação.

  • version: 1: Especifica a versão do formato de autoinstalação.

Este arquivo de configuração permite que o instalador do Ubuntu 24.04 execute uma instalação completamente automatizada, sem necessidade de intervenção manual.

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 Ubuntu 24.04 (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 Ubuntu usando o arquivo user-data
  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/ubuntu-24-amd64.raw /home/gean/kvm/templates/ubuntu-24-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" "ubpacker" {
  name      = "ubpacker"
  mode      = "nat"
  addresses = ["10.2.3.0/24"]
  autostart = true
  dhcp {
    enabled = false
  }
  dns {
    enabled = true
  }
}

Esta seção cria uma rede virtual chamada “ubpacker” 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.2.3.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   = "ubpacker.qcow2"
  pool   = "default"
  source = "/home/gean/kvm/templates/ubuntu-24-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. Note que a configuração de rede está comentada, o que significa que usaremos a configuração de rede padrão do sistema.

1
2
3
4
5
6
resource "libvirt_cloudinit_disk" "cloudinit_ubpacker" {
  name      = "cloudinit_ubpacker.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
31
32
resource "libvirt_domain" "ubpacker" {
  name   = "ubpacker"
  memory = "2048"
  vcpu   = 2

  cpu {
    mode = "host-passthrough"
  }

  cloudinit = libvirt_cloudinit_disk.cloudinit_ubpacker.id

  network_interface {
    #network_name = libvirt_network.ubpacker.name
    network_name   = "default"
    wait_for_lease = true
  }

  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 “ubpacker”
  • 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 “default” (em vez da rede “ubpacker” que criamos)
  • Console serial para acesso via linha de comando
  • Disco principal usando o volume criado anteriormente
  • Interface gráfica usando o protocolo SPICE

Note que estamos usando a rede “default” em vez da rede “ubpacker” que criamos. Isso é uma escolha de configuração que pode ser ajustada conforme suas necessidades. A opção wait_for_lease = true faz com que o Terraform aguarde até que a VM obtenha um endereço IP antes de considerar a criação concluída.

Agora, precisamos criar o arquivo de configuração do cloud-init:

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 ubpacker 

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

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.

Se quiser usar a configuração de rede estática, você pode descomentar as linhas relacionadas à configuração de rede no arquivo main.tf e criar o arquivo network_cfg.yml:

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.2.3.11/24
      nameservers:
        addresses: 
          - 10.2.3.1
      routes:
        - to: default
          via: 10.2.3.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.2.3.11/24
  • Usar 10.2.3.1 (gateway da rede NAT) como servidor DNS e gateway

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. Como configuramos wait_for_lease = true, o Terraform deve mostrar o endereço IP atribuído à VM. Você pode se conectar a ela via SSH usando:

1
ssh gean@<IP_DA_VM>

Ou, se estiver usando a configuração de rede estática:

1
ssh gean@10.2.3.11

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

1
virsh console ubpacker

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 Ubuntu 24.04 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 ufw (o firewall padrão do Ubuntu)
  • Implementar hardening de segurança seguindo as diretrizes de segurança do Ubuntu
  • 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 Ubuntu 24.04 LTS oferece várias vantagens para ambientes de produção, incluindo:

  • Suporte de longo prazo por cinco anos (até abril de 2029)
  • Atualizações regulares de segurança
  • Ampla comunidade de suporte
  • Compatibilidade com uma vasta gama de hardware e software
  • Integração com serviços de nuvem e ferramentas de DevOps

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