Post

Criação de Imagem Ubuntu com Packer e Cloud-init

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:

  1. Entender o que é o Cloud-init e como ele funciona
  2. Criar um arquivo Cloud-init personalizado para Ubuntu 24.04
  3. Configurar o Packer para utilizar o Cloud-init
  4. Adicionar scripts de provisionamento para personalização
  5. Construir e validar a imagem final
  6. 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:

  1. Inicialização: Detecta a plataforma de nuvem e configura a rede
  2. Configuração local: Aplica configurações específicas da instância
  3. Rede: Configura interfaces de rede
  4. 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 senha
    • password: Senha criptografada para o usuário (neste caso, “packer”)
    • username: Nome do usuário (packer)
  • locale: Define o idioma como português do Brasil
  • keyboard: 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 senha
  • ssh.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:

  1. Atualiza o sistema e instala o qemu-guest-agent
  2. Configura o cloud-init para usar o datasource NoCloud
  3. Remove pacotes desnecessários e limpa caches
  4. Remove logs e arquivos temporários
  5. Remove identificadores únicos da máquina (machine-id, chaves SSH)
  6. 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 resultante
  • disk_size: Tamanho do disco em MB (16GB neste caso)
  • iso_url: URL da ISO do Ubuntu 24.04
  • iso_checksum: Checksum SHA256 da ISO para verificação

Configuração QEMU

  • memory: 2GB de RAM para a VM
  • accelerator: Usa KVM para melhor desempenho
  • disk_interface e net_device: Usa dispositivos virtio para melhor desempenho
  • format: 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:

  1. Pressiona “c” para entrar no modo de comando do GRUB
  2. Carrega o kernel com parâmetros para autoinstall e datasource nocloud
  3. Configura os consoles
  4. Carrega o initrd
  5. 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:

  1. Copiam o script de limpeza para a VM
  2. Tornam o script executável
  3. 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á:

  1. Baixar a ISO do Ubuntu (se ainda não estiver em cache)
  2. Iniciar uma VM QEMU/KVM
  3. Inicializar a instalação com o Cloud-init
  4. Aguardar a conclusão da instalação
  5. Conectar-se via SSH
  6. Executar os provisionadores
  7. Desligar a VM
  8. 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ísticaCloud-initKickstartPreseed
DistribuiçõesMulti-distribuiçãoRed Hat/CentOS/OracleDebian/Ubuntu
FormatoYAMLBaseado em comandosPares chave-valor
FlexibilidadeAltaMédiaMédia
ComplexidadeMédiaBaixaAlta
Pós-instalaçãoExcelenteBoaLimitada
Integração com nuvemNativaLimitadaLimitada
DocumentaçãoExcelenteBoaBoa
Curva de aprendizadoMédiaBaixaAlta

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:

  1. Modifique o arquivo Cloud-init para instalar um ambiente de desktop mínimo (como GNOME ou KDE)
  2. Altere o particionamento para usar LVM com partições separadas para /, /var e /home
  3. Adicione configurações para instalar e configurar o Docker
  4. 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:

  1. O que é Cloud-init e como ele funciona
  2. Como criar um arquivo Cloud-init para automação da instalação
  3. Como configurar o Packer para usar o Cloud-init
  4. Como adicionar scripts de provisionamento para personalização
  5. Como construir e validar a imagem final
  6. 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.

Referências

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