Criação de Imagem Ubuntu com Packer e Cloud-init
Nota: Este tutorial é o quarto de uma série de 6 tutoriais sobre criação de imagens QEMU/KVM com Packer. Se você ainda não leu os tutoriais anteriores sobre os fundamentos do Packer, Oracle Linux com Kickstart e Debian com Preseed, recomendamos que o faça antes de prosseguir. Criação de Imagem Debian com Packer e Preseed.
Introdução
Nos tutoriais anteriores, exploramos os fundamentos do Packer e QEMU/KVM, e aprendemos a criar imagens automatizadas do Oracle Linux usando Kickstart e do Debian usando Preseed. Agora, vamos avançar para a criação de uma imagem do Ubuntu 24.04 (Noble Numbat) utilizando o método Cloud-init para automação da instalação.
O Ubuntu é uma das distribuições Linux mais populares, conhecida por sua facilidade de uso e ampla adoção em ambientes de desktop, servidor e nuvem. A versão 24.04 LTS (Long Term Support) oferece suporte por 5 anos, tornando-a uma excelente escolha para ambientes de produção.
Neste tutorial, vamos:
- Entender o que é o Cloud-init e como ele funciona
- Criar um arquivo Cloud-init personalizado para Ubuntu 24.04
- Configurar o Packer para utilizar o Cloud-init
- Adicionar scripts de provisionamento para personalização
- Construir e validar a imagem final
- Comparar Cloud-init com Kickstart e Preseed
O que é Cloud-init?
Cloud-init é um padrão de indústria para inicialização e configuração de instâncias em nuvem. Diferentemente do Kickstart e Preseed, que são específicos para famílias de distribuições Linux, o Cloud-init é uma solução multi-distribuição que funciona em praticamente todas as principais distribuições Linux e até mesmo em alguns sistemas operacionais BSD.
Originalmente desenvolvido pela Canonical (empresa por trás do Ubuntu), o Cloud-init tornou-se o método padrão para personalização de imagens em ambientes de nuvem como AWS, Azure, Google Cloud e OpenStack.
Como o Cloud-init funciona?
O Cloud-init opera em várias fases:
- Inicialização: Detecta a plataforma de nuvem e configura a rede
- Configuração local: Aplica configurações específicas da instância
- Rede: Configura interfaces de rede
- Configuração do usuário: Aplica configurações definidas pelo usuário
O Cloud-init obtém suas configurações de várias fontes, chamadas de “datasources”, que podem incluir:
- Metadados da plataforma de nuvem
- Arquivos de configuração locais
- Dados fornecidos via NoCloud (que usaremos neste tutorial)
Vantagens do Cloud-init
- Portabilidade: Funciona em múltiplas distribuições Linux
- Flexibilidade: Suporta vários formatos de configuração (YAML, JSON, etc.)
- Modularidade: Permite configurar apenas o que é necessário
- Extensibilidade: Pode ser estendido com módulos personalizados
- Adoção ampla: Padrão de fato para configuração de instâncias em nuvem
Preparando o Ambiente
Vamos preparar nosso ambiente de trabalho para este tutorial:
1
2
3
4
5
6
# Criar diretório para o quarto tutorial
mkdir -p ~/packer-kvm-tutorial/tutorial4
cd ~/packer-kvm-tutorial/tutorial4
# Criar subdiretórios necessários
mkdir -p http scripts build/os-base
Criando o Arquivo Cloud-init
Para o Ubuntu 24.04, usaremos o recurso de “autoinstall” que é baseado no Cloud-init. Vamos criar um arquivo user-data
no diretório http
que será servido via HTTP durante a instalação:
1
nano http/user-data
Adicione o seguinte conteúdo:
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 Cloud-init:
Configurações Básicas
identity
: Define o hostname, nome de usuário e senhapassword
: Senha criptografada para o usuário (neste caso, “packer”)username
: Nome do usuário (packer)
locale
: Define o idioma como português do Brasilkeyboard
: Define o layout de teclado como brasileiro
Configuração de Armazenamento
storage.layout.name
: Define o tipo de particionamento como “direct” (sem LVM)
Configuração de SSH
ssh.allow-pw
: Permite autenticação por senhassh.install-server
: Instala o servidor SSH
Configuração de Atualizações
updates
: Define para instalar apenas atualizações de segurança
Configuração de Fonte
source.id
: Define a variante de instalação como “ubuntu-server-minimal”
Também precisamos criar um arquivo meta-data
vazio, que é necessário para o Cloud-init:
1
touch http/meta-data
Criando o Script de Limpeza
Agora, vamos criar um script de limpeza que será executado após a instalação para otimizar a imagem:
1
nano scripts/cleanup.sh
Adicione o seguinte conteúdo:
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
#!/bin/bash -eux
# Atualizar o sistema
apt-get update
apt-get -y upgrade
# Instalar pacotes essenciais
apt-get -y install qemu-guest-agent
# Configurar cloud-init para NoCloud
cat > /etc/cloud/cloud.cfg.d/99-nocloud.cfg << EOF
datasource_list: [ NoCloud, None ]
EOF
# Limpar pacotes não necessários
apt-get -y autoremove
apt-get -y clean
# Limpar logs e arquivos temporários
find /var/log -type f -exec truncate --size=0 {} \;
rm -rf /tmp/*
rm -rf /var/tmp/*
# Remover informações específicas da máquina
truncate -s 0 /etc/machine-id
rm -f /etc/ssh/*key*
rm -f ~/.bash_history
# Limpar histórico de apt
rm -f /var/lib/apt/lists/*
# Zerar espaço livre
fstrim -av
Este script realiza várias tarefas importantes:
- Atualiza o sistema e instala o qemu-guest-agent
- Configura o cloud-init para usar o datasource NoCloud
- Remove pacotes desnecessários e limpa caches
- Remove logs e arquivos temporários
- Remove identificadores únicos da máquina (machine-id, chaves SSH)
- Executa fstrim para otimizar o espaço em disco
Torne o script executável:
1
chmod +x scripts/cleanup.sh
Criando o Arquivo Packer
Agora, vamos criar o arquivo Packer que utilizará o Cloud-init e o script de limpeza:
1
nano ubuntu24-kvm.pkr.hcl
Adicione o seguinte conteúdo:
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
packer {
required_version = ">= 1.10.0"
required_plugins {
qemu = {
version = "= 1.1.0"
source = "github.com/hashicorp/qemu"
}
}
}
variable "vm_name" {
default = "ubuntu-24.04-amd64.raw"
}
variable "disk_size" {
default = "16384"
}
variable "iso_url" {
default = "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
}
variable "iso_checksum" {
default = "sha256:c5ea60d25d64b3e3a47a6171c1dc78c94e29c5e6e8284b0c44b8a1feddc83e75"
}
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 = "5s"
boot_command = [
"c<wait>",
"linux /casper/vmlinuz --- autoinstall ds=nocloud-net\\;s=http://{{.HTTPIP}}:{{.HTTPPort}}/ ",
"console=tty1 console=ttyS0<enter><wait>",
"initrd /casper/initrd<enter><wait>",
"boot<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"
}
build {
sources = ["source.qemu.iso"]
# Envia o script de limpeza para a VM
provisioner "file" {
source = "scripts/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"
]
}
}
Vamos analisar as partes importantes deste arquivo:
Variáveis
vm_name
: Nome do arquivo de imagem resultantedisk_size
: Tamanho do disco em MB (16GB neste caso)iso_url
: URL da ISO do Ubuntu 24.04iso_checksum
: Checksum SHA256 da ISO para verificação
Configuração QEMU
memory
: 2GB de RAM para a VMaccelerator
: Usa KVM para melhor desempenhodisk_interface
enet_device
: Usa dispositivos virtio para melhor desempenhoformat
: Formato raw para a imagem resultante
Boot Command
1
2
3
4
5
6
7
boot_command = [
"c<wait>",
"linux /casper/vmlinuz --- autoinstall ds=nocloud-net\\;s=http://{{.HTTPIP}}:{{.HTTPPort}}/ ",
"console=tty1 console=ttyS0<enter><wait>",
"initrd /casper/initrd<enter><wait>",
"boot<enter>"
]
Esta sequência de comandos:
- Pressiona “c” para entrar no modo de comando do GRUB
- Carrega o kernel com parâmetros para autoinstall e datasource nocloud
- Configura os consoles
- Carrega o initrd
- Inicia o boot
Provisionadores
1
2
3
4
5
6
7
8
9
10
11
12
13
# Envia o script de limpeza para a VM
provisioner "file" {
source = "scripts/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"
]
}
Estes provisionadores:
- Copiam o script de limpeza para a VM
- Tornam o script executável
- Executam o script com privilégios sudo
Construindo a Imagem
Agora que temos todos os arquivos necessários, vamos construir nossa imagem:
1
2
3
4
5
6
7
8
# Inicializar o Packer (baixa os plugins necessários)
packer init .
# Validar a configuração
packer validate ubuntu24-kvm.pkr.hcl
# Construir a imagem (com logs detalhados)
PACKER_LOG=1 packer build ubuntu24-kvm.pkr.hcl
Este processo pode levar algum tempo, pois o Packer irá:
- Baixar a ISO do Ubuntu (se ainda não estiver em cache)
- Iniciar uma VM QEMU/KVM
- Inicializar a instalação com o Cloud-init
- Aguardar a conclusão da instalação
- Conectar-se via SSH
- Executar os provisionadores
- Desligar a VM
- Empacotar a imagem resultante
Verificando a Imagem Gerada
Após a conclusão do build, você terá uma imagem raw no diretório build/os-base
:
1
ls -lh build/os-base/
Você deve ver um arquivo como ubuntu-24.04-amd64.raw
.
Convertendo para QCOW2 (Opcional)
O formato raw é ótimo para desempenho, mas o formato QCOW2 oferece recursos adicionais como compressão e snapshots. Para converter a imagem:
1
qemu-img convert -O qcow2 -c build/os-base/ubuntu-24.04-amd64.raw ubuntu-24.04-amd64.qcow2
Testando a Imagem
Para testar a imagem, você pode iniciar uma VM usando o QEMU diretamente:
1
2
3
4
5
6
7
8
9
10
qemu-system-x86_64 \
-name "Test-Ubuntu24" \
-machine type=q35,accel=kvm \
-cpu host \
-m 2048 \
-drive file=build/os-base/ubuntu-24.04-amd64.raw,format=raw,if=virtio \
-net nic,model=virtio \
-net user,hostfwd=tcp::2222-:22 \
-display none \
-daemonize
Conecte-se à VM via SSH:
1
ssh -p 2222 packer@localhost
Comparação entre Cloud-init, Kickstart e Preseed
Agora que já trabalhamos com os três métodos de automação, vamos compará-los:
Característica | Cloud-init | Kickstart | Preseed |
---|---|---|---|
Distribuições | Multi-distribuição | Red Hat/CentOS/Oracle | Debian/Ubuntu |
Formato | YAML | Baseado em comandos | Pares chave-valor |
Flexibilidade | Alta | Média | Média |
Complexidade | Média | Baixa | Alta |
Pós-instalação | Excelente | Boa | Limitada |
Integração com nuvem | Nativa | Limitada | Limitada |
Documentação | Excelente | Boa | Boa |
Curva de aprendizado | Média | Baixa | Alta |
Quando usar cada método?
- Cloud-init: Ideal para ambientes de nuvem e quando você precisa de portabilidade entre distribuições
- Kickstart: Melhor para ambientes Red Hat/CentOS/Oracle Linux e quando você precisa de scripts pós-instalação flexíveis
- Preseed: Preferível para ambientes Debian/Ubuntu quando você precisa de controle detalhado sobre o processo de instalação
Personalizações Avançadas do Cloud-init
Vamos explorar algumas personalizações avançadas que você pode fazer no arquivo Cloud-init:
Particionamento Avançado
Para um particionamento mais detalhado:
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
storage:
layout:
name: lvm
config:
- type: disk
id: disk0
ptable: gpt
path: /dev/vda
- type: partition
id: boot-partition
device: disk0
size: 1G
flag: boot
- type: partition
id: root-partition
device: disk0
size: 100%
- type: format
id: boot-format
fstype: ext4
volume: boot-partition
- type: format
id: root-format
fstype: ext4
volume: root-partition
- type: mount
id: boot-mount
device: boot-format
path: /boot
- type: mount
id: root-mount
device: root-format
path: /
Configuração de Rede Avançada
Para configuração de rede estática:
1
2
3
4
5
6
7
8
9
network:
version: 2
ethernets:
enp1s0:
dhcp4: no
addresses: [192.168.1.100/24]
gateway4: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
Instalação de Pacotes Adicionais
Para instalar mais pacotes:
1
2
3
4
5
6
7
8
packages:
- vim
- git
- curl
- wget
- htop
- net-tools
- python3-pip
Execução de Comandos Personalizados
Para executar comandos após a instalação:
1
2
3
4
5
runcmd:
- [systemctl, enable, qemu-guest-agent]
- [systemctl, start, qemu-guest-agent]
- [sed, -i, 's/#PermitRootLogin prohibit-password/PermitRootLogin no/', /etc/ssh/sshd_config]
- [systemctl, restart, ssh]
Configuração de Usuários e Grupos
Para configurar usuários e grupos adicionais:
1
2
3
4
5
6
7
users:
- name: admin
groups: sudo
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2E...
Exercícios Práticos
Para fixar o conhecimento adquirido neste tutorial, tente os seguintes exercícios:
- Modifique o arquivo Cloud-init para instalar um ambiente de desktop mínimo (como GNOME ou KDE)
- Altere o particionamento para usar LVM com partições separadas para
/
,/var
e/home
- Adicione configurações para instalar e configurar o Docker
- Crie uma variável adicional no arquivo Packer para permitir a escolha entre Ubuntu 22.04 e 24.04
Solução de Problemas Comuns
A instalação não inicia com o Cloud-init
Verifique:
- Se o boot_command está correto e completo
- Se os arquivos user-data e meta-data estão no diretório http
- Se há erros de sintaxe no arquivo YAML (a indentação é crucial)
Timeout de SSH
Se o Packer não conseguir se conectar via SSH:
- Aumente o valor de
ssh_timeout
- Verifique se o usuário e senha no Cloud-init correspondem aos definidos no Packer
- Verifique se o SSH está instalado e habilitado no Cloud-init
Erros no script de limpeza
Se o script de limpeza falhar:
- Verifique permissões do script
- Verifique se todos os comandos são compatíveis com Ubuntu 24.04
- Tente executar os comandos manualmente para identificar o problema
Conclusão
Neste tutorial, aprendemos a criar uma imagem automatizada do Ubuntu 24.04 usando Packer e Cloud-init. Exploramos:
- O que é Cloud-init e como ele funciona
- Como criar um arquivo Cloud-init para automação da instalação
- Como configurar o Packer para usar o Cloud-init
- Como adicionar scripts de provisionamento para personalização
- Como construir e validar a imagem final
- As diferenças entre Cloud-init, Kickstart e Preseed
O Cloud-init oferece uma abordagem moderna e flexível para automação de instalações, especialmente adequada para ambientes de nuvem e quando você precisa de portabilidade entre diferentes distribuições Linux.
No próximo tutorial, vamos explorar técnicas avançadas de personalização de imagens, incluindo adição de pacotes personalizados, configuração de serviços específicos, hardening de segurança e otimização de desempenho.