Packer — budowanie spójnych obrazów VM dla VPS i chmury
Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / DevOps
Wdrażanie na serwerze przez ręczne uruchamianie skryptów lub Ansible jest wolne i niespójne — każdy serwer może mieć subtelne różnice wynikające z kolejności operacji lub stanów pośrednich. Packer rozwiązuje ten problem przez koncepcję "złotego obrazu" (golden image): budujesz obraz VM raz, testujesz go, a potem tworzysz identyczne serwery z tego obrazu. Czas startu nowego serwera spada z minut (Ansible provisioning) do sekund (start z gotowego snapshotu). Ten artykuł pokazuje budowanie obrazów dla Hetzner Cloud i lokalne z QEMU.
Instalacja Packer
# Ubuntu / Debian — oficjalne repo HashiCorp wget -O- https://apt.releases.hashicorp.com/gpg | \ sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install packer -y # macOS brew tap hashicorp/tap brew install hashicorp/tap/packer # Windows (Chocolatey) choco install packer # Sprawdź wersję packer version # Packer v1.10.x # Zainstaluj pluginy (wymagane dla Packer >= 1.7) packer plugins install github.com/hashicorp/hcloud packer plugins install github.com/hashicorp/qemu
Struktura szablonu HCL2
Packer od wersji 1.7 używa HCL2 (ten sam język co Terraform). Szablon składa się z bloków source (builder + konfiguracja VM) i build
(provisioners + post-processors). Oto struktura przykładowego projektu:
# Struktura projektu
# packer-ubuntu/
# ├── variables.pkr.hcl # Zmienne wejściowe
# ├── ubuntu.pkr.hcl # Główny szablon
# └── scripts/
# ├── base.sh # Hardening i narzędzia bazowe
# ├── nginx.sh # Nginx i PHP-FPM
# └── cleanup.sh # Czyszczenie cache apt, logi itp.
# variables.pkr.hcl
variable "hcloud_token" {
type = string
sensitive = true
}
variable "image_name" {
type = string
default = "ubuntu-22-04-lemp"
}
variable "server_type" {
type = string
default = "cx11" # Najtańszy do budowania
} Builder dla Hetzner Cloud — snapshoty VPS
# ubuntu.pkr.hcl — pełny szablon
packer {
required_plugins {
hcloud = {
version = ">= 1.2.0"
source = "github.com/hashicorp/hcloud"
}
}
}
# Source blok — konfiguracja VM do zbudowania
source "hcloud" "ubuntu-lemp" {
token = var.hcloud_token
image = "ubuntu-22.04"
location = "nbg1"
server_type = var.server_type
server_name = "packer-build-${formatdate("YYYYMMDD-hhmmss", timestamp())}"
# Snapshot name po zakończeniu budowania
snapshot_name = "${var.image_name}-${formatdate("YYYYMMDD", timestamp())}"
snapshot_labels = {
"managed-by" = "packer"
"os" = "ubuntu-22.04"
"stack" = "lemp"
}
# SSH do provisioning
communicator = "ssh"
ssh_username = "root"
# Timeout na uruchomienie VM
ssh_timeout = "10m"
}
# Build blok — co zrobić wewnątrz VM
build {
name = "lemp-ubuntu"
sources = ["source.hcloud.ubuntu-lemp"]
# Krok 1: aktualizacja systemu i hardening bazowy
provisioner "shell" {
scripts = [
"./scripts/base.sh",
]
# Czekaj na uruchomienie cloud-init
execute_command = "cloud-init status --wait && {{.Vars}} bash '{{.Path}}'"
}
# Krok 2: instalacja LEMP przez Ansible
provisioner "ansible" {
playbook_file = "./ansible/lemp.yml"
extra_arguments = [
"--extra-vars", "env=production",
"--ssh-extra-args", "-o StrictHostKeyChecking=no"
]
ansible_env_vars = [
"ANSIBLE_FORCE_COLOR=1",
]
}
# Krok 3: czyszczenie przed snapshot
provisioner "shell" {
inline = [
"apt-get clean",
"rm -rf /var/lib/apt/lists/*",
"rm -f /root/.ssh/authorized_keys",
"cloud-init clean --logs",
"sync",
]
}
# Post-processor: zapisz metadata do pliku manifest
post-processor "manifest" {
output = "manifest.json"
strip_path = true
custom_data = {
builder = "hcloud"
stack = "lemp"
}
}
} Skrypty provisioningu — base.sh
# scripts/base.sh — hardening bazowy Ubuntu #!/bin/bash set -euo pipefail # Aktualizacja systemu export DEBIAN_FRONTEND=noninteractive apt-get update -q apt-get upgrade -yq # Narzędzia bazowe apt-get install -yq \ curl wget git unzip htop vim \ fail2ban ufw \ logrotate \ software-properties-common \ apt-transport-https ca-certificates gnupg # Konfiguracja UFW — domyślnie blokuj wszystko ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw --force enable # SSH hardening sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/#PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config # Swap 2GB (dla małych VPS) if [ ! -f /swapfile ]; then fallocate -l 2G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile echo '/swapfile none swap sw 0 0' >> /etc/fstab fi # Parametry systemu cat >> /etc/sysctl.d/99-server.conf <<'SYSCTL' vm.swappiness=10 net.core.somaxconn=65535 net.ipv4.tcp_max_syn_backlog=65535 SYSCTL sysctl -p /etc/sysctl.d/99-server.conf
Builder QEMU — lokalne obrazy dla on-premise
# Packer builder QEMU — dla Proxmox / KVM / lokalnych serwerów
source "qemu" "ubuntu-22-04" {
iso_url = "https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso"
iso_checksum = "sha256:45f873de9f8cb637345d6e66a583762730bbea30277ef7b32c9c3bd6700a32b2"
disk_size = "20G"
disk_image = false
format = "qcow2"
output_directory = "output-ubuntu"
vm_name = "ubuntu-22.04.qcow2"
# Parametry VM podczas budowania
cpus = 2
memory = 2048
headless = true
# Boot sequence — instalacja przez cloud-init
boot_wait = "5s"
boot_command = [
"<esc><wait>",
"linux /casper/vmlinuz quiet autoinstall ds=nocloud;s=http://{{.HTTPIP}}:{{.HTTPPort}}/ --- <enter>",
"initrd /casper/initrd <enter>",
"boot <enter>",
]
# HTTP server do udostępnienia preseed/user-data
http_directory = "http"
http_port_min = 8080
http_port_max = 9090
communicator = "ssh"
ssh_username = "ubuntu"
ssh_password = "ubuntu"
ssh_timeout = "30m"
shutdown_command = "sudo systemctl poweroff"
} Budowanie i integracja z Terraform
# Inicjalizacja i walidacja szablonu
packer init ubuntu.pkr.hcl
packer validate ubuntu.pkr.hcl
# Budowanie obrazu
HCLOUD_TOKEN=xxx packer build ubuntu.pkr.hcl
# Lub przez zmienną
packer build \
-var "hcloud_token=$HCLOUD_TOKEN" \
-var "image_name=ubuntu-lemp-v2" \
ubuntu.pkr.hcl
# Wynik: manifest.json z ID snapshotu
# {
# "builds": [{
# "artifact_id": "51234567",
# "custom_data": { "stack": "lemp" }
# }]
# }
# Terraform — użycie Packer snapshot przez data source
data "hcloud_image" "lemp" {
with_selector = "managed-by=packer,stack=lemp"
most_recent = true
}
resource "hcloud_server" "web" {
name = "web-prod-01"
image = data.hcloud_image.lemp.id # ID ze snapshotu Packer
server_type = "cx21"
location = "nbg1"
ssh_keys = [data.hcloud_ssh_key.main.id]
}