Post

Integração com Pipelines CI/CD e Solução de Problemas com Packer

Integração com Pipelines CI/CD e Solução de Problemas com Packer

Nota: Este tutorial é o sexto e último 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, recomendamos que o faça antes de prosseguir. Criação de Imagem Ubuntu com Packer e Cloud-init.

Introdução

Nos tutoriais anteriores, exploramos os fundamentos do Packer e QEMU/KVM, aprendemos a criar imagens automatizadas para diferentes distribuições Linux (Oracle Linux, Debian e Ubuntu) e dominamos técnicas avançadas de personalização. Agora, chegamos ao último tutorial da série, onde vamos abordar dois tópicos cruciais para ambientes profissionais:

  1. Integração com Pipelines CI/CD: Como automatizar a criação de imagens como parte de um fluxo de integração e entrega contínua
  2. Solução de Problemas Avançados: Técnicas para diagnosticar e resolver problemas comuns e complexos com o Packer

A integração do Packer em pipelines CI/CD permite automatizar completamente o processo de criação de imagens, garantindo consistência, rastreabilidade e facilitando a adoção de práticas de infraestrutura como código (IaC). Já o domínio de técnicas avançadas de solução de problemas é essencial para lidar com situações inesperadas e garantir a robustez do seu processo de criação de imagens.

Neste tutorial, vamos:

  1. Entender os princípios de CI/CD aplicados à criação de imagens
  2. Implementar pipelines em diferentes plataformas (GitLab CI, GitHub Actions e Jenkins)
  3. Criar estratégias de teste para validar imagens
  4. Explorar técnicas avançadas de depuração e solução de problemas
  5. Aprender a otimizar pipelines para maior eficiência

Princípios de CI/CD para Criação de Imagens

Antes de mergulharmos nas implementações específicas, vamos entender os princípios fundamentais de CI/CD aplicados à criação de imagens:

1. Versionamento de Código

Todo o código relacionado à criação de imagens deve ser versionado em um sistema de controle de versão como Git:

  • Arquivos de configuração do Packer (.pkr.hcl)
  • Scripts de provisionamento
  • Arquivos de automação (Kickstart, Preseed, Cloud-init)
  • Arquivos de configuração de serviços
  • Pipelines CI/CD

2. Automação Completa

O processo de criação de imagens deve ser completamente automatizado, sem intervenção manual:

  • Download de ISOs
  • Validação de configurações
  • Build de imagens
  • Testes
  • Publicação de imagens

3. Testes Automatizados

As imagens geradas devem ser testadas automaticamente para garantir sua qualidade:

  • Testes de inicialização
  • Verificação de serviços
  • Testes de segurança
  • Validação de configurações

4. Imutabilidade

As imagens devem ser tratadas como artefatos imutáveis:

  • Uma vez criada, uma imagem não deve ser modificada
  • Alterações devem resultar em novas versões de imagens
  • Versões anteriores devem ser preservadas

5. Rastreabilidade

Deve ser possível rastrear cada imagem até seu código-fonte:

  • Metadados de versão
  • Commit que gerou a imagem
  • Parâmetros de build
  • Resultados de testes

Preparando o Ambiente

Vamos preparar nosso ambiente de trabalho para este tutorial:

1
2
3
4
5
6
# Criar diretório para o sexto tutorial
mkdir -p ~/packer-kvm-tutorial/tutorial6
cd ~/packer-kvm-tutorial/tutorial6

# Criar subdiretórios necessários
mkdir -p http scripts tests ci

Estrutura de Projeto para CI/CD

Para facilitar a integração com pipelines CI/CD, vamos organizar nosso projeto com a seguinte estrutura:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.
├── README.md                  # Documentação do projeto
├── packer/                    # Arquivos do Packer
│   ├── ubuntu/                # Configuração para Ubuntu
│   │   ├── ubuntu.pkr.hcl     # Arquivo principal do Packer
│   │   ├── variables.pkr.hcl  # Variáveis do Packer
│   │   └── http/              # Arquivos HTTP (Cloud-init)
│   ├── debian/                # Configuração para Debian
│   └── oracle/                # Configuração para Oracle Linux
├── scripts/                   # Scripts de provisionamento
│   ├── common/                # Scripts comuns a todas as distribuições
│   ├── ubuntu/                # Scripts específicos para Ubuntu
│   ├── debian/                # Scripts específicos para Debian
│   └── oracle/                # Scripts específicos para Oracle Linux
├── tests/                     # Testes automatizados
│   ├── boot_test.sh           # Teste de inicialização
│   ├── services_test.sh       # Teste de serviços
│   └── security_test.sh       # Teste de segurança
└── ci/                        # Configurações de CI/CD
    ├── gitlab-ci.yml          # Configuração do GitLab CI
    ├── github-workflow.yml    # Configuração do GitHub Actions
    └── Jenkinsfile            # Configuração do Jenkins

Vamos criar alguns desses arquivos para demonstrar a integração com CI/CD.

Arquivo Packer Otimizado para CI/CD

Primeiro, vamos criar um arquivo Packer otimizado para CI/CD:

1
2
mkdir -p packer/ubuntu/http
nano packer/ubuntu/ubuntu.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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
packer {
  required_version = ">= 1.10.0" 
  required_plugins {
    qemu = {
      version = "= 1.1.0"
      source  = "github.com/hashicorp/qemu"
    }
  }
}

# Variáveis locais para uso interno
locals {
  build_timestamp = formatdate("YYYYMMDDhhmmss", timestamp())
  image_name      = "${var.image_prefix}-${var.ubuntu_version}-${local.build_timestamp}"
}

# Fonte da imagem
source "qemu" "ubuntu" {
  vm_name          = "${local.image_name}.raw"
  iso_url          = var.iso_url
  iso_checksum     = var.iso_checksum
  disk_size        = var.disk_size
  memory           = var.memory
  cpus             = var.cpus
  disk_image       = false
  output_directory = var.output_directory
  accelerator      = "kvm"
  disk_interface   = "virtio"
  format           = "raw"
  net_device       = "virtio-net"
  boot_wait        = "5s"
  boot_command     = var.boot_command
  http_directory   = "${path.root}/http"
  cpu_model        = "host"
  shutdown_command = "echo '${var.ssh_password}' | sudo -S shutdown -P now"
  ssh_username     = var.ssh_username
  ssh_password     = var.ssh_password
  ssh_timeout      = var.ssh_timeout
  
  # Adicionar metadados à imagem
  qemu_binary      = "qemu-system-x86_64"
  qemuargs         = [
    ["-smbios", "type=1,serial=${var.image_prefix}-${var.ubuntu_version}-${local.build_timestamp}"]
  ]
}

# Build da imagem
build {
  name    = "ubuntu-server"
  sources = ["source.qemu.ubuntu"]

  # Adicionar metadados à imagem
  provisioner "shell" {
    inline = [
      "echo '${var.image_prefix}-${var.ubuntu_version}-${local.build_timestamp}' | sudo tee /etc/image_version",
      "echo 'Build Date: ${timestamp()}' | sudo tee -a /etc/image_version",
      "echo 'Builder: Packer ${packer.version}' | sudo tee -a /etc/image_version"
    ]
  }

  # Executar scripts de provisionamento
  provisioner "shell" {
    scripts = var.provisioning_scripts
  }

  # Converter para QCOW2 e comprimir
  post-processor "shell-local" {
    inline = [
      "qemu-img convert -O qcow2 -c ${var.output_directory}/${local.image_name}.raw ${var.output_directory}/${local.image_name}.qcow2",
      "rm ${var.output_directory}/${local.image_name}.raw"
    ]
  }

  # Gerar arquivo de manifesto
  post-processor "manifest" {
    output     = "${var.output_directory}/${local.image_name}-manifest.json"
    strip_path = true
    custom_data = {
      image_name    = local.image_name
      build_date    = timestamp()
      ubuntu_version = var.ubuntu_version
      builder       = "Packer ${packer.version}"
    }
  }
}

Agora, vamos criar o arquivo de variáveis:

1
nano packer/ubuntu/variables.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
72
73
74
75
76
77
78
79
80
81
82
# Variáveis básicas
variable "image_prefix" {
  type    = string
  default = "ubuntu-server"
}

variable "ubuntu_version" {
  type    = string
  default = "24.04"
}

variable "disk_size" {
  type    = string
  default = "20480"
}

variable "memory" {
  type    = string
  default = "2048"
}

variable "cpus" {
  type    = string
  default = "2"
}

# Variáveis de ISO
variable "iso_url" {
  type    = string
  default = "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
}

variable "iso_checksum" {
  type    = string
  default = "sha256:c5ea60d25d64b3e3a47a6171c1dc78c94e29c5e6e8284b0c44b8a1feddc83e75"
}

# Variáveis de SSH
variable "ssh_username" {
  type    = string
  default = "packer"
}

variable "ssh_password" {
  type    = string
  default = "packer"
}

variable "ssh_timeout" {
  type    = string
  default = "60m"
}

# Variáveis de boot
variable "boot_command" {
  type    = list(string)
  default = [
    "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>"
  ]
}

# Variáveis de output
variable "output_directory" {
  type    = string
  default = "output"
}

# Variáveis de provisionamento
variable "provisioning_scripts" {
  type    = list(string)
  default = [
    "../../scripts/common/update.sh",
    "../../scripts/common/install_packages.sh",
    "../../scripts/ubuntu/configure_services.sh",
    "../../scripts/common/security_hardening.sh",
    "../../scripts/common/cleanup.sh"
  ]
}

Vamos criar o arquivo Cloud-init para o Ubuntu:

1
nano packer/ubuntu/http/user-data

Adicione o conteúdo do arquivo Cloud-init que criamos no tutorial 4.

1
touch packer/ubuntu/http/meta-data

Scripts de Provisionamento

Vamos criar alguns scripts de provisionamento básicos:

1
2
mkdir -p scripts/common scripts/ubuntu
nano scripts/common/update.sh

Adicione o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    export DEBIAN_FRONTEND=noninteractive
    apt-get update
    apt-get upgrade -y
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    dnf update -y
fi
1
nano scripts/common/install_packages.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
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    export DEBIAN_FRONTEND=noninteractive
    apt-get install -y \
        qemu-guest-agent \
        cloud-init \
        curl \
        wget \
        vim \
        htop \
        net-tools
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    dnf install -y \
        qemu-guest-agent \
        cloud-init \
        curl \
        wget \
        vim \
        htop \
        net-tools
fi
1
nano scripts/common/cleanup.sh

Adicione o conteúdo do script de limpeza que criamos nos tutoriais anteriores.

Torne os scripts executáveis:

1
chmod +x scripts/common/*.sh scripts/ubuntu/*.sh

Testes Automatizados

Vamos criar alguns scripts de teste para validar nossas imagens:

1
2
mkdir -p tests
nano tests/boot_test.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
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
72
73
74
75
76
77
78
79
80
81
#!/bin/bash
set -e

# Parâmetros
IMAGE_PATH=$1
SSH_PORT=${2:-2222}
SSH_USER=${3:-packer}
SSH_KEY=${4:-""}

if [ -z "$IMAGE_PATH" ]; then
    echo "Uso: $0 <caminho_da_imagem> [porta_ssh] [usuário_ssh] [chave_ssh]"
    exit 1
fi

echo "Iniciando teste de boot para: $IMAGE_PATH"

# Iniciar a VM
if [ -n "$SSH_KEY" ]; then
    SSH_OPTS="-i $SSH_KEY"
else
    SSH_OPTS=""
fi

# Iniciar a VM com QEMU
VM_PID=$(qemu-system-x86_64 \
  -name "Test-VM" \
  -machine type=q35,accel=kvm \
  -cpu host \
  -m 2048 \
  -drive file=$IMAGE_PATH,format=qcow2,if=virtio \
  -net nic,model=virtio \
  -net user,hostfwd=tcp::$SSH_PORT-:22 \
  -display none \
  -daemonize \
  -pidfile /tmp/test_vm.pid | tee /dev/stderr | grep -o '[0-9]*')

echo "VM iniciada com PID: $VM_PID"

# Aguardar a VM iniciar
echo "Aguardando a VM iniciar..."
for i in {1..30}; do
    if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost echo "VM está online" 2>/dev/null; then
        echo "VM iniciou com sucesso após $i tentativas"
        
        # Executar testes básicos
        echo "Verificando sistema operacional..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "cat /etc/os-release"
        
        echo "Verificando versão do kernel..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "uname -a"
        
        echo "Verificando espaço em disco..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "df -h"
        
        echo "Verificando memória..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "free -m"
        
        # Desligar a VM
        echo "Desligando a VM..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo shutdown -h now" || true
        
        # Aguardar a VM desligar
        echo "Aguardando a VM desligar..."
        for j in {1..30}; do
            if ! kill -0 $VM_PID 2>/dev/null; then
                echo "VM desligou com sucesso"
                exit 0
            fi
            sleep 1
        done
        
        echo "Tempo esgotado aguardando a VM desligar, matando o processo..."
        kill -9 $VM_PID || true
        exit 1
    fi
    sleep 10
done

echo "Tempo esgotado aguardando a VM iniciar"
kill -9 $VM_PID || true
exit 1
1
nano tests/services_test.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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash
set -e

# Parâmetros
IMAGE_PATH=$1
SSH_PORT=${2:-2222}
SSH_USER=${3:-packer}
SSH_KEY=${4:-""}

if [ -z "$IMAGE_PATH" ]; then
    echo "Uso: $0 <caminho_da_imagem> [porta_ssh] [usuário_ssh] [chave_ssh]"
    exit 1
fi

echo "Iniciando teste de serviços para: $IMAGE_PATH"

# Iniciar a VM
if [ -n "$SSH_KEY" ]; then
    SSH_OPTS="-i $SSH_KEY"
else
    SSH_OPTS=""
fi

# Iniciar a VM com QEMU
VM_PID=$(qemu-system-x86_64 \
  -name "Test-VM" \
  -machine type=q35,accel=kvm \
  -cpu host \
  -m 2048 \
  -drive file=$IMAGE_PATH,format=qcow2,if=virtio \
  -net nic,model=virtio \
  -net user,hostfwd=tcp::$SSH_PORT-:22 \
  -display none \
  -daemonize \
  -pidfile /tmp/test_vm.pid | tee /dev/stderr | grep -o '[0-9]*')

echo "VM iniciada com PID: $VM_PID"

# Aguardar a VM iniciar
echo "Aguardando a VM iniciar..."
for i in {1..30}; do
    if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost echo "VM está online" 2>/dev/null; then
        echo "VM iniciou com sucesso após $i tentativas"
        
        # Verificar serviços essenciais
        echo "Verificando serviço SSH..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo systemctl status sshd" || { echo "Falha no serviço SSH"; exit 1; }
        
        echo "Verificando serviço cloud-init..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo systemctl status cloud-init" || { echo "Falha no serviço cloud-init"; exit 1; }
        
        echo "Verificando serviço qemu-guest-agent..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo systemctl status qemu-guest-agent" || { echo "Falha no serviço qemu-guest-agent"; exit 1; }
        
        # Verificar serviços opcionais (se existirem)
        echo "Verificando serviço nginx (se instalado)..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo systemctl status nginx" || echo "Nginx não instalado ou não está em execução"
        
        echo "Verificando serviço php-fpm (se instalado)..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo systemctl status php*-fpm" || echo "PHP-FPM não instalado ou não está em execução"
        
        # Desligar a VM
        echo "Desligando a VM..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo shutdown -h now" || true
        
        # Aguardar a VM desligar
        echo "Aguardando a VM desligar..."
        for j in {1..30}; do
            if ! kill -0 $VM_PID 2>/dev/null; then
                echo "VM desligou com sucesso"
                exit 0
            fi
            sleep 1
        done
        
        echo "Tempo esgotado aguardando a VM desligar, matando o processo..."
        kill -9 $VM_PID || true
        exit 1
    fi
    sleep 10
done

echo "Tempo esgotado aguardando a VM iniciar"
kill -9 $VM_PID || true
exit 1
1
nano tests/security_test.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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/bin/bash
set -e

# Parâmetros
IMAGE_PATH=$1
SSH_PORT=${2:-2222}
SSH_USER=${3:-packer}
SSH_KEY=${4:-""}

if [ -z "$IMAGE_PATH" ]; then
    echo "Uso: $0 <caminho_da_imagem> [porta_ssh] [usuário_ssh] [chave_ssh]"
    exit 1
fi

echo "Iniciando teste de segurança para: $IMAGE_PATH"

# Iniciar a VM
if [ -n "$SSH_KEY" ]; then
    SSH_OPTS="-i $SSH_KEY"
else
    SSH_OPTS=""
fi

# Iniciar a VM com QEMU
VM_PID=$(qemu-system-x86_64 \
  -name "Test-VM" \
  -machine type=q35,accel=kvm \
  -cpu host \
  -m 2048 \
  -drive file=$IMAGE_PATH,format=qcow2,if=virtio \
  -net nic,model=virtio \
  -net user,hostfwd=tcp::$SSH_PORT-:22 \
  -display none \
  -daemonize \
  -pidfile /tmp/test_vm.pid | tee /dev/stderr | grep -o '[0-9]*')

echo "VM iniciada com PID: $VM_PID"

# Aguardar a VM iniciar
echo "Aguardando a VM iniciar..."
for i in {1..30}; do
    if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost echo "VM está online" 2>/dev/null; then
        echo "VM iniciou com sucesso após $i tentativas"
        
        # Verificar configurações de SSH
        echo "Verificando configurações de SSH..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo grep PermitRootLogin /etc/ssh/sshd_config" || { echo "Falha ao verificar PermitRootLogin"; exit 1; }
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo grep PasswordAuthentication /etc/ssh/sshd_config" || { echo "Falha ao verificar PasswordAuthentication"; exit 1; }
        
        # Verificar firewall
        echo "Verificando firewall..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo ufw status || sudo firewall-cmd --state || echo 'Nenhum firewall detectado'" || { echo "Falha ao verificar firewall"; exit 1; }
        
        # Verificar atualizações de segurança
        echo "Verificando atualizações de segurança..."
        if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "test -f /etc/debian_version"; then
            ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo apt-get -s upgrade | grep -i security" || echo "Nenhuma atualização de segurança pendente"
        elif ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "test -f /etc/redhat-release"; then
            ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo dnf check-update --security" || echo "Nenhuma atualização de segurança pendente"
        fi
        
        # Verificar configurações de sysctl
        echo "Verificando configurações de sysctl..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo sysctl -a | grep -E 'net.ipv4.conf.all.rp_filter|net.ipv4.conf.all.accept_source_route|net.ipv4.tcp_syncookies'" || { echo "Falha ao verificar configurações de sysctl"; exit 1; }
        
        # Desligar a VM
        echo "Desligando a VM..."
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $SSH_PORT $SSH_OPTS $SSH_USER@localhost "sudo shutdown -h now" || true
        
        # Aguardar a VM desligar
        echo "Aguardando a VM desligar..."
        for j in {1..30}; do
            if ! kill -0 $VM_PID 2>/dev/null; then
                echo "VM desligou com sucesso"
                exit 0
            fi
            sleep 1
        done
        
        echo "Tempo esgotado aguardando a VM desligar, matando o processo..."
        kill -9 $VM_PID || true
        exit 1
    fi
    sleep 10
done

echo "Tempo esgotado aguardando a VM iniciar"
kill -9 $VM_PID || true
exit 1

Torne os scripts de teste executáveis:

1
chmod +x tests/*.sh

Configurações de CI/CD

Agora, vamos criar as configurações para diferentes plataformas de CI/CD:

GitLab CI

1
2
mkdir -p ci
nano ci/gitlab-ci.yml

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
stages:
  - validate
  - build
  - test
  - publish

variables:
  PACKER_CACHE_DIR: "${CI_PROJECT_DIR}/.packer_cache"
  OUTPUT_DIR: "${CI_PROJECT_DIR}/output"
  PACKER_LOG: "1"
  PACKER_LOG_PATH: "${CI_PROJECT_DIR}/packer.log"

# Job para validar a configuração do Packer
validate:
  stage: validate
  image: hashicorp/packer:1.10.0
  script:
    - cd packer/ubuntu
    - packer init .
    - packer validate -var "output_directory=${OUTPUT_DIR}" .
  tags:
    - docker

# Job para construir a imagem
build:
  stage: build
  image: hashicorp/packer:1.10.0
  script:
    - mkdir -p ${OUTPUT_DIR}
    - cd packer/ubuntu
    - packer init .
    - packer build -var "output_directory=${OUTPUT_DIR}" .
  artifacts:
    paths:
      - output/*.qcow2
      - output/*-manifest.json
      - packer.log
    expire_in: 1 week
  tags:
    - kvm
  only:
    - main
    - tags
  dependencies:
    - validate

# Job para testar a imagem
test:
  stage: test
  image: ubuntu:24.04
  script:
    - apt-get update
    - apt-get install -y qemu-system-x86 openssh-client
    - IMAGE_PATH=$(find ${OUTPUT_DIR} -name "*.qcow2" | head -n 1)
    - ./tests/boot_test.sh ${IMAGE_PATH}
    - ./tests/services_test.sh ${IMAGE_PATH}
    - ./tests/security_test.sh ${IMAGE_PATH}
  tags:
    - kvm
  only:
    - main
    - tags
  dependencies:
    - build

# Job para publicar a imagem
publish:
  stage: publish
  image: alpine:latest
  script:
    - apk add --no-cache curl
    - IMAGE_PATH=$(find ${OUTPUT_DIR} -name "*.qcow2" | head -n 1)
    - IMAGE_NAME=$(basename ${IMAGE_PATH})
    - MANIFEST_PATH=$(find ${OUTPUT_DIR} -name "*-manifest.json" | head -n 1)
    - |
      if [[ -n "${CI_COMMIT_TAG}" ]]; then
        VERSION=${CI_COMMIT_TAG}
      else
        VERSION=${CI_COMMIT_SHORT_SHA}
      fi
    - |
      echo "Publicando imagem ${IMAGE_NAME} com versão ${VERSION}"
      # Aqui você adicionaria comandos para publicar a imagem em um repositório
      # Por exemplo, usando curl para fazer upload para um servidor de artefatos
      # curl -T ${IMAGE_PATH} "https://artifacts.example.com/images/${VERSION}/${IMAGE_NAME}"
      # curl -T ${MANIFEST_PATH} "https://artifacts.example.com/images/${VERSION}/$(basename ${MANIFEST_PATH})"
  tags:
    - docker
  only:
    - main
    - tags
  dependencies:
    - test

GitHub Actions

1
nano ci/github-workflow.yml

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
name: Build and Test Images

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Packer
        uses: hashicorp/setup-packer@main
        with:
          version: '1.10.0'

      - name: Validate Packer configuration
        run: |
          cd packer/ubuntu
          packer init .
          packer validate .

  build:
    needs: validate
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Packer
        uses: hashicorp/setup-packer@main
        with:
          version: '1.10.0'

      - name: Build image
        run: |
          mkdir -p output
          cd packer/ubuntu
          packer init .
          PACKER_LOG=1 PACKER_LOG_PATH=../../packer.log packer build -var "output_directory=../../output" .

      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: images
          path: |
            output/*.qcow2
            output/*-manifest.json
            packer.log

  test:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: images
          path: output

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y qemu-system-x86 openssh-client

      - name: Run tests
        run: |
          chmod +x tests/*.sh
          IMAGE_PATH=$(find output -name "*.qcow2" | head -n 1)
          ./tests/boot_test.sh ${IMAGE_PATH}
          ./tests/services_test.sh ${IMAGE_PATH}
          ./tests/security_test.sh ${IMAGE_PATH}

  publish:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: images
          path: output

      - name: Set version
        id: version
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
          else
            echo "VERSION=${GITHUB_SHA::7}" >> $GITHUB_ENV
          fi

      - name: Publish image
        run: |
          IMAGE_PATH=$(find output -name "*.qcow2" | head -n 1)
          IMAGE_NAME=$(basename ${IMAGE_PATH})
          MANIFEST_PATH=$(find output -name "*-manifest.json" | head -n 1)
          
          echo "Publicando imagem ${IMAGE_NAME} com versão ${VERSION}"
          # Aqui você adicionaria comandos para publicar a imagem em um repositório
          # Por exemplo, usando curl para fazer upload para um servidor de artefatos
          # curl -T ${IMAGE_PATH} "https://artifacts.example.com/images/${VERSION}/${IMAGE_NAME}"
          # curl -T ${MANIFEST_PATH} "https://artifacts.example.com/images/${VERSION}/$(basename ${MANIFEST_PATH})"

Jenkins

1
nano ci/Jenkinsfile

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
pipeline {
    agent {
        label 'kvm'
    }
    
    environment {
        PACKER_CACHE_DIR = "${WORKSPACE}/.packer_cache"
        OUTPUT_DIR = "${WORKSPACE}/output"
        PACKER_LOG = "1"
        PACKER_LOG_PATH = "${WORKSPACE}/packer.log"
    }
    
    stages {
        stage('Validate') {
            agent {
                docker {
                    image 'hashicorp/packer:1.10.0'
                    args '-v ${WORKSPACE}:/workspace'
                }
            }
            steps {
                sh '''
                cd /workspace/packer/ubuntu
                packer init .
                packer validate -var "output_directory=${OUTPUT_DIR}" .
                '''
            }
        }
        
        stage('Build') {
            agent {
                docker {
                    image 'hashicorp/packer:1.10.0'
                    args '-v ${WORKSPACE}:/workspace --privileged'
                }
            }
            steps {
                sh '''
                mkdir -p ${OUTPUT_DIR}
                cd /workspace/packer/ubuntu
                packer init .
                packer build -var "output_directory=${OUTPUT_DIR}" .
                '''
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                IMAGE_PATH=$(find ${OUTPUT_DIR} -name "*.qcow2" | head -n 1)
                chmod +x tests/*.sh
                ./tests/boot_test.sh ${IMAGE_PATH}
                ./tests/services_test.sh ${IMAGE_PATH}
                ./tests/security_test.sh ${IMAGE_PATH}
                '''
            }
        }
        
        stage('Publish') {
            when {
                anyOf {
                    branch 'main'
                    tag '*'
                }
            }
            steps {
                script {
                    def imagePath = sh(script: "find ${OUTPUT_DIR} -name '*.qcow2' | head -n 1", returnStdout: true).trim()
                    def imageName = sh(script: "basename ${imagePath}", returnStdout: true).trim()
                    def manifestPath = sh(script: "find ${OUTPUT_DIR} -name '*-manifest.json' | head -n 1", returnStdout: true).trim()
                    
                    def version = ""
                    if (env.TAG_NAME) {
                        version = env.TAG_NAME
                    } else {
                        version = env.GIT_COMMIT.substring(0, 7)
                    }
                    
                    echo "Publicando imagem ${imageName} com versão ${version}"
                    // Aqui você adicionaria comandos para publicar a imagem em um repositório
                    // Por exemplo, usando curl para fazer upload para um servidor de artefatos
                    // sh "curl -T ${imagePath} https://artifacts.example.com/images/${version}/${imageName}"
                    // sh "curl -T ${manifestPath} https://artifacts.example.com/images/${version}/$(basename ${manifestPath})"
                }
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: 'output/*.qcow2, output/*-manifest.json, packer.log', allowEmptyArchive: true
        }
    }
}

Técnicas Avançadas de Solução de Problemas

Agora, vamos explorar algumas técnicas avançadas de solução de problemas com o Packer.

1. Depuração com Logs Detalhados

O Packer oferece logs detalhados que podem ajudar a diagnosticar problemas:

1
PACKER_LOG=1 PACKER_LOG_PATH=packer.log packer build template.pkr.hcl

Para analisar os logs, procure por padrões específicos:

1
2
3
4
5
6
7
8
# Erros de SSH
grep -i "ssh" packer.log | grep -i "error"

# Erros de QEMU
grep -i "qemu" packer.log | grep -i "error"

# Erros de provisionamento
grep -i "provisioner" packer.log | grep -i "error"

2. Modo de Depuração Interativo

O Packer oferece um modo de depuração interativo que pausa a execução em pontos-chave:

1
PACKER_LOG=1 packer build -debug template.pkr.hcl

No modo de depuração, o Packer pausa antes de cada etapa e aguarda sua confirmação para continuar. Isso permite que você inspecione o estado da VM em cada etapa.

3. Captura de Tela da VM

Para problemas durante a instalação, você pode capturar telas da VM:

1
2
3
4
5
6
7
8
9
10
source "qemu" "debug" {
  # Configurações básicas...
  
  # Habilitar interface gráfica
  headless = false
  
  # Capturar telas em intervalos
  vnc_port_min = 5900
  vnc_port_max = 5900
}

Você também pode usar o VNC para conectar-se à VM durante o build:

1
vncviewer localhost:5900

4. Análise de Falhas de Boot

Para problemas de boot, você pode modificar o boot_command para incluir pausas mais longas:

1
2
3
4
5
6
7
boot_command = [
  "c<wait10>",  # Espera 10 segundos após pressionar 'c'
  "linux /casper/vmlinuz --- autoinstall ds=nocloud-net\\;s=http://{{.HTTPIP}}:{{.HTTPPort}}/ <wait5>",
  "console=tty1 console=ttyS0<enter><wait10>",
  "initrd /casper/initrd<enter><wait10>",
  "boot<enter>"
]

5. Solução de Problemas de Rede

Para problemas de rede, você pode usar configurações de rede alternativas:

1
2
3
4
5
6
7
source "qemu" "debug" {
  # Configurações básicas...
  
  # Usar bridge em vez de user mode networking
  net_device = "virtio-net"
  net_bridge = "virbr0"
}

6. Solução de Problemas de SSH

Para problemas de SSH, você pode aumentar o timeout e habilitar logs detalhados:

1
2
3
4
5
6
7
source "qemu" "debug" {
  # Configurações básicas...
  
  ssh_timeout = "60m"
  ssh_handshake_attempts = 100
  ssh_pty = true
}

7. Solução de Problemas de Provisionamento

Para problemas de provisionamento, você pode usar o provisionador shell-local para depurar:

1
2
3
4
5
6
7
8
provisioner "shell-local" {
  inline = [
    "echo 'Estado atual da VM:'",
    "echo 'SSH disponível na porta: ${build.SSHPort}'",
    "echo 'Pressione Enter para continuar...'"
  ]
  pause_before = "10s"
}

8. Criando um Script de Diagnóstico

Vamos criar um script de diagnóstico que pode ser executado dentro da VM para coletar informações:

1
nano scripts/common/diagnose.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/bin/bash -eux

# Criar diretório para diagnóstico
DIAG_DIR="/tmp/packer_diagnose"
mkdir -p $DIAG_DIR

# Informações do sistema
echo "Coletando informações do sistema..."
uname -a > $DIAG_DIR/uname.txt
cat /etc/os-release > $DIAG_DIR/os-release.txt
lsb_release -a > $DIAG_DIR/lsb-release.txt 2>/dev/null || echo "lsb_release não disponível" > $DIAG_DIR/lsb-release.txt

# Informações de hardware
echo "Coletando informações de hardware..."
lscpu > $DIAG_DIR/lscpu.txt
free -m > $DIAG_DIR/memory.txt
df -h > $DIAG_DIR/disk.txt
lsblk -a > $DIAG_DIR/lsblk.txt
lspci > $DIAG_DIR/lspci.txt 2>/dev/null || echo "lspci não disponível" > $DIAG_DIR/lspci.txt

# Informações de rede
echo "Coletando informações de rede..."
ip addr > $DIAG_DIR/ip-addr.txt
ip route > $DIAG_DIR/ip-route.txt
cat /etc/resolv.conf > $DIAG_DIR/resolv.conf.txt
netstat -tulpn > $DIAG_DIR/netstat.txt 2>/dev/null || ss -tulpn > $DIAG_DIR/ss.txt

# Logs do sistema
echo "Coletando logs do sistema..."
journalctl -b -n 1000 > $DIAG_DIR/journalctl.txt 2>/dev/null || echo "journalctl não disponível" > $DIAG_DIR/journalctl.txt
dmesg > $DIAG_DIR/dmesg.txt
if [ -d /var/log ]; then
    find /var/log -type f -name "*.log" -exec cp {} $DIAG_DIR/ \; 2>/dev/null
fi

# Informações de serviços
echo "Coletando informações de serviços..."
systemctl list-units --type=service > $DIAG_DIR/systemctl-services.txt 2>/dev/null || echo "systemctl não disponível" > $DIAG_DIR/systemctl-services.txt

# Informações de pacotes
echo "Coletando informações de pacotes..."
if command -v dpkg > /dev/null; then
    dpkg -l > $DIAG_DIR/dpkg-packages.txt
elif command -v rpm > /dev/null; then
    rpm -qa > $DIAG_DIR/rpm-packages.txt
fi

# Compactar tudo
echo "Compactando arquivos de diagnóstico..."
tar -czf /tmp/packer_diagnose.tar.gz -C /tmp packer_diagnose

echo "Diagnóstico concluído. Arquivo disponível em /tmp/packer_diagnose.tar.gz"

Torne o script executável:

1
chmod +x scripts/common/diagnose.sh

Para usar este script durante o build do Packer:

1
2
3
4
5
6
7
8
9
10
provisioner "shell" {
  script = "scripts/common/diagnose.sh"
  pause_before = "10s"
}

provisioner "file" {
  source = "/tmp/packer_diagnose.tar.gz"
  destination = "packer_diagnose.tar.gz"
  direction = "download"
}

Otimizando Pipelines CI/CD

Vamos explorar algumas técnicas para otimizar pipelines CI/CD para criação de imagens:

1. Caching de ISOs

Para evitar o download repetido de ISOs, configure o cache do Packer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# GitLab CI
cache:
  paths:
    - .packer_cache/

# GitHub Actions
- name: Cache Packer
  uses: actions/cache@v3
  with:
    path: |
      .packer_cache
    key: ${{ runner.os }}-packer-${{ hashFiles('packer/**/*.hcl') }}
    restore-keys: |
      ${{ runner.os }}-packer-

2. Builds Paralelos

Para construir várias imagens em paralelo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# GitLab CI
build-ubuntu:
  stage: build
  script:
    - cd packer/ubuntu
    - packer build .

build-debian:
  stage: build
  script:
    - cd packer/debian
    - packer build .

# GitHub Actions
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu, debian, oracle]
    steps:
      - name: Build image
        run: |
          cd packer/${{ matrix.os }}
          packer build .

3. Builds Incrementais

Para reduzir o tempo de build, você pode usar builds incrementais:

1
2
3
4
5
6
7
8
source "qemu" "incremental" {
  # Configurações básicas...
  
  # Usar uma imagem base existente
  disk_image = true
  iso_url = "https://artifacts.example.com/images/base-ubuntu-24.04.qcow2"
  iso_checksum = "sha256:..."
}

4. Testes Automatizados Eficientes

Para tornar os testes mais eficientes:

1
2
3
4
5
# Executar testes em paralelo
parallel --jobs 3 ./tests/{} ::: boot_test.sh services_test.sh security_test.sh

# Usar timeouts menores
SSH_TIMEOUT=30 ./tests/boot_test.sh image.qcow2

5. Notificações e Relatórios

Para manter a equipe informada sobre o status dos builds:

1
2
3
4
5
6
7
8
9
10
11
# GitLab CI
notify:
  stage: .post
  script:
    - |
      if [ "$CI_JOB_STATUS" == "success" ]; then
        curl -X POST -H 'Content-type: application/json' --data '{"text":"Build bem-sucedido: '$CI_PROJECT_URL'/pipelines/'$CI_PIPELINE_ID'"}' $SLACK_WEBHOOK_URL
      else
        curl -X POST -H 'Content-type: application/json' --data '{"text":"Build falhou: '$CI_PROJECT_URL'/pipelines/'$CI_PIPELINE_ID'"}' $SLACK_WEBHOOK_URL
      fi
  when: always

Versionamento de Imagens

Uma prática importante é versionar suas imagens de forma consistente:

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
locals {
  timestamp = formatdate("YYYYMMDDhhmmss", timestamp())
  version   = "${var.image_prefix}-${var.os_version}-${local.timestamp}"
}

source "qemu" "template" {
  vm_name = "${local.version}.raw"
  # ...
}

build {
  # ...
  
  post-processor "manifest" {
    output     = "${var.output_directory}/${local.version}-manifest.json"
    strip_path = true
    custom_data = {
      version     = local.version
      build_date  = timestamp()
      git_commit  = "${env("GIT_COMMIT")}"
      git_branch  = "${env("GIT_BRANCH")}"
      builder     = "Packer ${packer.version}"
    }
  }
}

Exercícios Práticos

Para fixar o conhecimento adquirido neste tutorial, tente os seguintes exercícios:

  1. Configure um pipeline CI/CD completo em uma das plataformas (GitLab CI, GitHub Actions ou Jenkins)
  2. Implemente testes automatizados adicionais para validar suas imagens
  3. Crie um script de diagnóstico personalizado para sua distribuição Linux preferida
  4. Implemente um sistema de versionamento para suas imagens
  5. Otimize o tempo de build usando técnicas de caching e builds incrementais

Solução de Problemas Comuns em CI/CD

Problemas de Permissão em Ambientes CI/CD

Em ambientes CI/CD, você pode encontrar problemas de permissão ao executar o QEMU:

1
Error: Failed to initialize machine 'source.qemu.template': error initializing machine for driver 'qemu': exec: "qemu-system-x86_64": permission denied

Solução:

  • Use a flag --privileged ao executar o container Docker
  • Configure o runner para ter acesso ao KVM (/dev/kvm)

Timeouts em Ambientes CI/CD

Os builds podem demorar mais em ambientes CI/CD:

1
Timeout waiting for SSH.

Solução:

  • Aumente o valor de ssh_timeout
  • Verifique se a VM está iniciando corretamente
  • Use o modo de depuração para identificar o problema

Problemas de Rede em Ambientes CI/CD

Problemas de rede podem impedir o acesso à VM:

1
Error waiting for SSH: dial tcp 127.0.0.1:2222: connect: connection refused

Solução:

  • Verifique se o redirecionamento de porta está funcionando
  • Use net_bridge em vez de net_device se disponível
  • Verifique se o SSH está instalado e em execução na VM

Conclusão

Neste tutorial final, exploramos a integração do Packer com pipelines CI/CD e técnicas avançadas de solução de problemas. Aprendemos a:

  1. Configurar pipelines CI/CD em diferentes plataformas (GitLab CI, GitHub Actions e Jenkins)
  2. Implementar testes automatizados para validar imagens
  3. Usar técnicas avançadas de depuração e solução de problemas
  4. Otimizar pipelines para maior eficiência
  5. Versionar imagens de forma consistente

A automação da criação de imagens com Packer e sua integração em pipelines CI/CD permite criar infraestrutura como código de forma eficiente, consistente e reproduzível. As técnicas de solução de problemas apresentadas ajudam a diagnosticar e resolver problemas rapidamente, garantindo a robustez do processo.

Esperamos que esta série de tutoriais tenha fornecido uma base sólida para você começar a criar suas próprias imagens automatizadas com Packer e QEMU/KVM. Lembre-se de que a prática é essencial para dominar essas ferramentas, então experimente, adapte e expanda os exemplos apresentados para atender às suas necessidades específicas.

Referências

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