Post

Personalização Avançada de Imagens com Packer

Personalização Avançada de Imagens com Packer

Nota: Este tutorial é o quinto 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 e a criação de imagens para Oracle Linux, Debian e Ubuntu, 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, e aprendemos a criar imagens automatizadas para Oracle Linux, Debian e Ubuntu usando diferentes métodos de automação (Kickstart, Preseed e Cloud-init). Agora, vamos avançar para técnicas avançadas de personalização de imagens.

Criar uma imagem básica é apenas o primeiro passo. Para ambientes de produção, você geralmente precisa personalizar suas imagens com pacotes específicos, configurações de segurança, otimizações de desempenho e muito mais. Neste tutorial, vamos explorar como transformar uma imagem básica em uma imagem pronta para produção.

Neste tutorial, vamos:

  1. Explorar diferentes métodos de provisionamento no Packer
  2. Adicionar pacotes personalizados às imagens
  3. Configurar serviços específicos
  4. Implementar hardening de segurança básico
  5. Otimizar o desempenho das imagens
  6. Criar imagens multi-propósito com variáveis

Métodos de Provisionamento no Packer

O Packer oferece vários “provisionadores” que permitem personalizar suas imagens de diferentes maneiras. Vamos explorar os mais úteis:

Provisionador shell

O provisionador shell executa comandos shell diretamente na máquina virtual durante o processo de build. É o método mais simples e direto para personalização.

1
2
3
4
5
6
7
provisioner "shell" {
  inline = [
    "apt-get update",
    "apt-get install -y nginx",
    "systemctl enable nginx"
  ]
}

Você também pode executar scripts externos:

1
2
3
provisioner "shell" {
  script = "scripts/setup.sh"
}

Ou vários scripts em sequência:

1
2
3
4
5
6
7
8
provisioner "shell" {
  scripts = [
    "scripts/update.sh",
    "scripts/install_packages.sh",
    "scripts/configure_services.sh",
    "scripts/cleanup.sh"
  ]
}

Provisionador file

O provisionador file copia arquivos da máquina host para a máquina virtual. É útil para transferir arquivos de configuração, scripts ou outros recursos.

1
2
3
4
5
6
7
8
provisioner "file" {
  source      = "files/nginx.conf"
  destination = "/tmp/nginx.conf"
}

provisioner "shell" {
  inline = ["sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf"]
}

Você também pode copiar diretórios inteiros:

1
2
3
4
provisioner "file" {
  source      = "files/app/"
  destination = "/tmp/app"
}

Provisionador ansible

O provisionador ansible executa playbooks Ansible na máquina virtual. É uma opção poderosa para configurações complexas.

1
2
3
provisioner "ansible" {
  playbook_file = "ansible/playbook.yml"
}

Provisionador ansible-local

O provisionador ansible-local instala o Ansible na máquina virtual e executa playbooks localmente. É útil quando você não pode executar o Ansible diretamente na máquina host.

1
2
3
4
5
provisioner "ansible-local" {
  playbook_file   = "ansible/playbook.yml"
  playbook_dir    = "ansible"
  extra_arguments = ["--extra-vars", "\"variable1=value1 variable2=value2\""]
}

Preparando o Ambiente

Vamos preparar nosso ambiente de trabalho para este tutorial:

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

# Criar subdiretórios necessários
mkdir -p http scripts files ansible build/os-base

Adicionando Pacotes Personalizados

Uma das personalizações mais comuns é adicionar pacotes específicos às suas imagens. Vamos criar um script que instala pacotes comuns para um servidor web:

1
nano scripts/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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/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
    apt-get install -y \
        nginx \
        php-fpm \
        php-mysql \
        mariadb-client \
        fail2ban \
        ufw \
        htop \
        vim \
        git \
        curl \
        wget \
        unzip \
        net-tools \
        python3-pip
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    dnf update -y
    dnf install -y \
        nginx \
        php-fpm \
        php-mysqlnd \
        mariadb \
        fail2ban \
        firewalld \
        htop \
        vim \
        git \
        curl \
        wget \
        unzip \
        net-tools \
        python3-pip
fi

# Instalar pacotes Python comuns
pip3 install \
    requests \
    boto3 \
    awscli \
    ansible

Torne o script executável:

1
chmod +x scripts/install_packages.sh

Configurando Serviços Específicos

Agora, vamos criar um script para configurar o Nginx e o PHP-FPM:

1
nano scripts/configure_services.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
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    NGINX_CONF="/etc/nginx/sites-available/default"
    PHP_FPM_CONF="/etc/php/*/fpm/pool.d/www.conf"
    SYSTEMD_CMD="systemctl"
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    NGINX_CONF="/etc/nginx/conf.d/default.conf"
    PHP_FPM_CONF="/etc/php-fpm.d/www.conf"
    SYSTEMD_CMD="systemctl"
fi

# Configurar Nginx
cat > $NGINX_CONF << 'EOF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    
    root /var/www/html;
    index index.php index.html index.htm;
    
    server_name _;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php-fpm.sock;
    }
    
    location ~ /\.ht {
        deny all;
    }
}
EOF

# Configurar PHP-FPM
sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/*/fpm/php.ini 2>/dev/null || true
sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php.ini 2>/dev/null || true

# Criar página de teste
mkdir -p /var/www/html
cat > /var/www/html/index.php << 'EOF'
<?php
phpinfo();
EOF

# Ajustar permissões
chown -R www-data:www-data /var/www/html 2>/dev/null || true
chown -R nginx:nginx /var/www/html 2>/dev/null || true

# Habilitar e iniciar serviços
$SYSTEMD_CMD enable nginx
$SYSTEMD_CMD enable php-fpm

Torne o script executável:

1
chmod +x scripts/configure_services.sh

Implementando Hardening de Segurança

A segurança é crucial para imagens de produção. Vamos criar um script de hardening básico:

1
nano scripts/security_hardening.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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    FIREWALL_CMD="ufw"
    SSH_CONF="/etc/ssh/sshd_config"
    SYSTEMD_CMD="systemctl"
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    FIREWALL_CMD="firewall-cmd"
    SSH_CONF="/etc/ssh/sshd_config"
    SYSTEMD_CMD="systemctl"
fi

# Configurar SSH
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' $SSH_CONF
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' $SSH_CONF
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' $SSH_CONF
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' $SSH_CONF
sed -i 's/X11Forwarding yes/X11Forwarding no/' $SSH_CONF
sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding no/' $SSH_CONF
sed -i 's/#AllowTcpForwarding yes/AllowTcpForwarding no/' $SSH_CONF
echo "Protocol 2" >> $SSH_CONF
echo "ClientAliveInterval 300" >> $SSH_CONF
echo "ClientAliveCountMax 2" >> $SSH_CONF

# Configurar limites de recursos
cat > /etc/security/limits.conf << 'EOF'
* soft nofile 65536
* hard nofile 65536
* soft nproc 4096
* hard nproc 4096
EOF

# Configurar sysctl para segurança
cat > /etc/sysctl.d/99-security.conf << 'EOF'
# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Block SYN attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
EOF

# Aplicar configurações sysctl
sysctl -p /etc/sysctl.d/99-security.conf

# Configurar firewall
if [ "$FIREWALL_CMD" = "ufw" ]; then
    # UFW (Ubuntu/Debian)
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw allow http
    ufw allow https
    echo "y" | ufw enable
elif [ "$FIREWALL_CMD" = "firewall-cmd" ]; then
    # firewalld (RHEL/CentOS/Oracle)
    $SYSTEMD_CMD enable firewalld
    $SYSTEMD_CMD start firewalld
    firewall-cmd --permanent --add-service=ssh
    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https
    firewall-cmd --reload
fi

# Configurar fail2ban
if command -v fail2ban-client &> /dev/null; then
    $SYSTEMD_CMD enable fail2ban
    $SYSTEMD_CMD start fail2ban
    
    # Configuração básica do fail2ban
    cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5

[sshd]
enabled = true
EOF
fi

Torne o script executável:

1
chmod +x scripts/security_hardening.sh

Otimizando o Desempenho

Vamos criar um script para otimizar o desempenho do sistema:

1
nano scripts/performance_tuning.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
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    SYSTEMD_CMD="systemctl"
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    SYSTEMD_CMD="systemctl"
fi

# Configurar sysctl para desempenho
cat > /etc/sysctl.d/99-performance.conf << 'EOF'
# Aumentar o tamanho do buffer de rede
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.optmem_max = 65536
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Aumentar o número máximo de conexões
net.core.somaxconn = 65536
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_max_tw_buckets = 65536

# Reutilização de sockets em TIME_WAIT
net.ipv4.tcp_tw_reuse = 1

# Otimizações de TCP
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_fastopen = 3
EOF

# Aplicar configurações sysctl
sysctl -p /etc/sysctl.d/99-performance.conf

# Configurar limites para o Nginx
if command -v nginx &> /dev/null; then
    mkdir -p /etc/systemd/system/nginx.service.d/
    cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65536
EOF
    $SYSTEMD_CMD daemon-reload
fi

# Otimizar configuração do Nginx
if [ -f /etc/nginx/nginx.conf ]; then
    sed -i 's/worker_processes.*/worker_processes auto;/' /etc/nginx/nginx.conf
    sed -i 's/# multi_accept.*/multi_accept on;/' /etc/nginx/nginx.conf
    sed -i 's/# worker_connections.*/worker_connections 4096;/' /etc/nginx/nginx.conf
    
    # Adicionar configurações de cache e gzip se não existirem
    if ! grep -q "open_file_cache" /etc/nginx/nginx.conf; then
        sed -i '/http {/a \    open_file_cache max=1000 inactive=20s;\n    open_file_cache_valid 30s;\n    open_file_cache_min_uses 2;\n    open_file_cache_errors on;' /etc/nginx/nginx.conf
    fi
    
    if ! grep -q "gzip" /etc/nginx/nginx.conf; then
        sed -i '/http {/a \    gzip on;\n    gzip_vary on;\n    gzip_proxied any;\n    gzip_comp_level 6;\n    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;' /etc/nginx/nginx.conf
    fi
fi

# Otimizar configuração do PHP-FPM
if [ -f /etc/php/*/fpm/pool.d/www.conf ]; then
    # Debian/Ubuntu
    PHP_FPM_CONF=$(find /etc/php -name www.conf)
    sed -i 's/pm = dynamic/pm = ondemand/' $PHP_FPM_CONF
    sed -i 's/pm.max_children = 5/pm.max_children = 50/' $PHP_FPM_CONF
    sed -i 's/pm.start_servers = 2/pm.start_servers = 10/' $PHP_FPM_CONF
    sed -i 's/pm.min_spare_servers = 1/pm.min_spare_servers = 5/' $PHP_FPM_CONF
    sed -i 's/pm.max_spare_servers = 3/pm.max_spare_servers = 20/' $PHP_FPM_CONF
    sed -i 's/;pm.max_requests = 500/pm.max_requests = 500/' $PHP_FPM_CONF
elif [ -f /etc/php-fpm.d/www.conf ]; then
    # RHEL/CentOS/Oracle Linux
    sed -i 's/pm = dynamic/pm = ondemand/' /etc/php-fpm.d/www.conf
    sed -i 's/pm.max_children = 5/pm.max_children = 50/' /etc/php-fpm.d/www.conf
    sed -i 's/pm.start_servers = 2/pm.start_servers = 10/' /etc/php-fpm.d/www.conf
    sed -i 's/pm.min_spare_servers = 1/pm.min_spare_servers = 5/' /etc/php-fpm.d/www.conf
    sed -i 's/pm.max_spare_servers = 3/pm.max_spare_servers = 20/' /etc/php-fpm.d/www.conf
    sed -i 's/;pm.max_requests = 500/pm.max_requests = 500/' /etc/php-fpm.d/www.conf
fi

Torne o script executável:

1
chmod +x scripts/performance_tuning.sh

Criando um Script de Limpeza Final

Finalmente, vamos criar um script de limpeza para otimizar a imagem final:

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
34
35
36
37
38
#!/bin/bash -eux

# Detectar o sistema operacional
if [ -f /etc/debian_version ]; then
    # Debian/Ubuntu
    export DEBIAN_FRONTEND=noninteractive
    apt-get autoremove -y
    apt-get clean
    rm -rf /var/lib/apt/lists/*
elif [ -f /etc/redhat-release ]; then
    # RHEL/CentOS/Oracle Linux
    dnf clean all
    rm -rf /var/cache/dnf/*
fi

# 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

# Configurar cloud-init para NoCloud
if command -v cloud-init &> /dev/null; then
    cat > /etc/cloud/cloud.cfg.d/99-nocloud.cfg << 'EOF'
datasource_list: [ NoCloud, None ]
EOF
fi

# Zerar espaço livre
dd if=/dev/zero of=/EMPTY bs=1M || true
rm -f /EMPTY

# Sincronizar sistema de arquivos
sync

Torne o script executável:

1
chmod +x scripts/cleanup.sh

Criando o Arquivo Packer com Variáveis

Agora, vamos criar um arquivo Packer que utiliza variáveis para criar imagens multi-propósito:

1
nano advanced-template.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
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
packer {
  required_version = ">= 1.10.0" 
  required_plugins {
    qemu = {
      version = "= 1.1.0"
      source  = "github.com/hashicorp/qemu"
    }
  }
}

variable "vm_name" {
  type    = string
  default = "advanced-template.raw"
}

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

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

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

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"
}

variable "os_type" {
  type    = string
  default = "ubuntu"
  validation {
    condition     = contains(["ubuntu", "debian", "oracle"], var.os_type)
    error_message = "The os_type must be one of: ubuntu, debian, oracle."
  }
}

variable "install_web_server" {
  type    = bool
  default = true
}

variable "enable_security_hardening" {
  type    = bool
  default = true
}

variable "enable_performance_tuning" {
  type    = bool
  default = true
}

locals {
  boot_command = {
    ubuntu = [
      "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>"
    ],
    debian = [
      "<esc><wait>",
      "install <wait>",
      "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg <wait>",
      "debian-installer=pt_BR.UTF-8 <wait>",
      "auto <wait>",
      "locale=pt_BR.UTF-8 <wait>",
      "kbd-chooser/method=br <wait>",
      "keyboard-configuration/xkb-keymap=br <wait>",
      "netcfg/get_hostname=packer <wait>",
      "netcfg/get_domain=local <wait>",
      "fb=false <wait>",
      "debconf/frontend=noninteractive <wait>",
      "console-setup/ask_detect=false <wait>",
      "console-keymaps-at/keymap=br <wait>",
      "<enter><wait>"
    ],
    oracle = [
      "<up>",
      "<tab><wait>",
      " inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/ks.cfg",
      "<enter>"
    ]
  }
  
  http_directory = {
    ubuntu = "http/ubuntu",
    debian = "http/debian",
    oracle = "http/oracle"
  }
}

source "qemu" "template" {
  vm_name          = var.vm_name 
  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 = "build/os-base"
  accelerator      = "kvm"
  disk_interface   = "virtio"
  format           = "raw"
  net_device       = "virtio-net"
  boot_wait        = "5s"
  boot_command     = local.boot_command[var.os_type]
  http_directory   = local.http_directory[var.os_type]
  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.template"]

  # Instalar pacotes personalizados (condicional)
  provisioner "shell" {
    script = "scripts/install_packages.sh"
    only   = [var.install_web_server ? "qemu.template" : ""]
  }

  # Configurar serviços (condicional)
  provisioner "shell" {
    script = "scripts/configure_services.sh"
    only   = [var.install_web_server ? "qemu.template" : ""]
  }

  # Aplicar hardening de segurança (condicional)
  provisioner "shell" {
    script = "scripts/security_hardening.sh"
    only   = [var.enable_security_hardening ? "qemu.template" : ""]
  }

  # Aplicar otimizações de desempenho (condicional)
  provisioner "shell" {
    script = "scripts/performance_tuning.sh"
    only   = [var.enable_performance_tuning ? "qemu.template" : ""]
  }

  # Executar limpeza final
  provisioner "shell" {
    script = "scripts/cleanup.sh"
  }
}

Este arquivo Packer usa variáveis e condicionais para criar imagens personalizadas com base em diferentes parâmetros:

  • os_type: Tipo de sistema operacional (ubuntu, debian, oracle)
  • install_web_server: Se deve instalar e configurar um servidor web
  • enable_security_hardening: Se deve aplicar configurações de segurança
  • enable_performance_tuning: Se deve aplicar otimizações de desempenho

Criando Arquivos de Automação para Diferentes Sistemas

Para que o template funcione com diferentes sistemas operacionais, precisamos criar os diretórios e arquivos de automação:

1
mkdir -p http/ubuntu http/debian http/oracle

Para Ubuntu (Cloud-init)

1
nano http/ubuntu/user-data

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

1
touch http/ubuntu/meta-data

Para Debian (Preseed)

1
nano http/debian/preseed.cfg

Adicione o conteúdo do arquivo Preseed que criamos no tutorial anterior.

Para Oracle Linux (Kickstart)

1
nano http/oracle/ks.cfg

Adicione o conteúdo do arquivo Kickstart que criamos no tutorial anterior.

Usando o Template Avançado

Agora podemos usar nosso template avançado para criar diferentes tipos de imagens:

Imagem Ubuntu Básica

1
2
3
4
5
6
packer build -var "os_type=ubuntu" \
             -var "vm_name=ubuntu-basic.raw" \
             -var "install_web_server=false" \
             -var "enable_security_hardening=false" \
             -var "enable_performance_tuning=false" \
             advanced-template.pkr.hcl

Imagem Ubuntu com Servidor Web

1
2
3
4
5
6
packer build -var "os_type=ubuntu" \
             -var "vm_name=ubuntu-web.raw" \
             -var "install_web_server=true" \
             -var "enable_security_hardening=true" \
             -var "enable_performance_tuning=true" \
             advanced-template.pkr.hcl

Imagem Debian com Servidor Web

1
2
3
4
5
6
7
8
packer build -var "os_type=debian" \
             -var "vm_name=debian-web.raw" \
             -var "iso_url=https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso" \
             -var "iso_checksum=sha256:013f5b44670d81280b5b1bc02455842b247951bf8ba45f6.." \
             -var "install_web_server=true" \
             -var "enable_security_hardening=true" \
             -var "enable_performance_tuning=true" \
             advanced-template.pkr.hcl

Imagem Oracle Linux com Servidor Web

1
2
3
4
5
6
7
8
packer build -var "os_type=oracle" \
             -var "vm_name=oracle-web.raw" \
             -var "iso_url=https://yum.oracle.com/ISOS/OracleLinux/OL9/u4/x86_64/OracleLinux-R9-U4-x86_64-boot.iso" \
             -var "iso_checksum=sha256:975de11be8761efa4aa2c87d7d3bedcb62c9dc956909b68f62c99062d11599e9" \
             -var "install_web_server=true" \
             -var "enable_security_hardening=true" \
             -var "enable_performance_tuning=true" \
             advanced-template.pkr.hcl

Usando Arquivos de Variáveis

Para simplificar ainda mais, podemos criar arquivos de variáveis para diferentes cenários:

1
nano ubuntu-web.pkrvars.hcl

Adicione o seguinte conteúdo:

1
2
3
4
5
6
7
os_type                  = "ubuntu"
vm_name                  = "ubuntu-web.raw"
iso_url                  = "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
iso_checksum             = "sha256:c5ea60d25d64b3e3a47a6171c1dc78c94e29c5e6e8284b0c44b8a1feddc83e75"
install_web_server       = true
enable_security_hardening = true
enable_performance_tuning = true

Agora podemos usar este arquivo de variáveis:

1
packer build -var-file=ubuntu-web.pkrvars.hcl advanced-template.pkr.hcl

Técnicas Avançadas de Personalização

Usando Ansible para Configuração Avançada

Para configurações mais complexas, o Ansible é uma excelente opção. Vamos criar um playbook básico:

1
nano ansible/playbook.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
---
- name: Configure Web Server
  hosts: all
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: Install Nginx
      package:
        name: nginx
        state: present

    - name: Install PHP-FPM
      package:
        name:
          - php-fpm
          - php-mysql
        state: present

    - name: Configure Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      when: ansible_os_family == "Debian"

    - name: Configure Nginx (RHEL)
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/conf.d/default.conf
      when: ansible_os_family == "RedHat"

    - name: Enable and start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

    - name: Enable and start PHP-FPM
      service:
        name: "{{ 'php-fpm' if ansible_os_family == 'RedHat' else 'php7.4-fpm' }}"
        state: started
        enabled: yes

Crie o diretório de templates e o arquivo de configuração do Nginx:

1
2
mkdir -p ansible/templates
nano ansible/templates/nginx.conf.j2

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
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    
    root /var/www/html;
    index index.php index.html index.htm;
    
    server_name _;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php-fpm.sock;
    }
    
    location ~ /\.ht {
        deny all;
    }
}

Para usar o Ansible com o Packer, adicione o seguinte provisionador ao arquivo advanced-template.pkr.hcl:

1
2
3
4
5
provisioner "ansible-local" {
  playbook_file = "ansible/playbook.yml"
  playbook_dir  = "ansible"
  only          = [var.install_web_server ? "qemu.template" : ""]
}

Usando Post-Processors

Os post-processors do Packer permitem realizar ações após a criação da imagem. Por exemplo, podemos converter automaticamente a imagem para o formato QCOW2:

1
2
3
4
5
6
post-processor "shell-local" {
  inline = [
    "qemu-img convert -O qcow2 -c build/os-base/${var.vm_name} build/os-base/${var.vm_name}.qcow2",
    "rm build/os-base/${var.vm_name}"
  ]
}

Exercícios Práticos

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

  1. Crie um template personalizado para um servidor de banco de dados (MySQL/MariaDB)
  2. Adicione suporte para mais uma distribuição Linux (como Fedora ou CentOS)
  3. Implemente um provisionador Ansible para configurar um ambiente LAMP completo
  4. Crie um arquivo de variáveis para uma imagem otimizada para execução de containers Docker

Solução de Problemas Comuns

Erros nos Scripts de Provisionamento

Se os scripts de provisionamento falharem:

  • Verifique se os scripts têm permissão de execução
  • Verifique se os comandos são compatíveis com a distribuição Linux escolhida
  • Tente executar os comandos manualmente para identificar o problema

Problemas com Variáveis

Se houver problemas com as variáveis:

  • Verifique a sintaxe e os tipos de dados
  • Certifique-se de que os valores estão corretos
  • Use o comando packer validate para verificar a configuração

Problemas com Ansible

Se o provisionador Ansible falhar:

  • Verifique se o Ansible está instalado na máquina virtual
  • Verifique a sintaxe do playbook
  • Verifique se os caminhos dos arquivos estão corretos

Conclusão

Neste tutorial, exploramos técnicas avançadas de personalização de imagens com Packer. Aprendemos a:

  1. Usar diferentes métodos de provisionamento
  2. Adicionar pacotes personalizados às imagens
  3. Configurar serviços específicos
  4. Implementar hardening de segurança básico
  5. Otimizar o desempenho das imagens
  6. Criar imagens multi-propósito com variáveis

Estas técnicas permitem criar imagens altamente personalizadas e otimizadas para diferentes casos de uso, desde servidores web até ambientes de banco de dados ou containers.

No próximo e último tutorial da série, vamos explorar a integração do Packer com pipelines CI/CD e técnicas avançadas de solução de problemas.

Referências

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