使用 Packer 在 Proxmox 上自动化构建 Alpine Linux VM 模板

借助 Pumba98/proxmox-packer-templates 项目,一键自动化构建 Cloud-Init 就绪的 VM 模板。基于真实踩坑经验整理:每个变量在哪里找、TLS 证书报错怎么解决,小白照着做也能成功

前言

在 Proxmox VE 上创建虚拟机通常是一个重复劳动:挂载 ISO、手动安装系统、配置 SSH、装 QEMU Guest Agent……每次都要走一遍。

Pumba98/proxmox-packer-templates 这个开源项目用 HashiCorp Packer 把这一切自动化了。你只需要配置几个变量,一条命令就能生成一个开箱即用、Cloud-Init 就绪的 VM 模板。

这套教程围绕 Alpine Linux 最新版展开,讲清楚每一步是做什么、为什么这样做。即使你之前没用过 Packer,也能照着操作。

💡 本教程仅围绕 Alpine Linux,但同一项目也支持 Ubuntu、Debian、AlmaLinux、Rocky Linux、Windows Server 等多个系统。


项目结构速览

这个项目的设计思路非常清晰:

proxmox-packer-templates/
├── config.pkr.hcl          # Packer 插件配置(声明要用哪些插件)
├── generic.pkr.hcl         # 通用构建器(所有 OS 共享的构建逻辑)
├── variables.pkr.hcl       # 全部可配置变量的定义(含默认值)
├── alpine-3.24.pkrvars.hcl # Alpine 3.24 的变量配置(ISO、启动命令、安装后脚本)
├── alpine-3.23.pkrvars.hcl # Alpine 3.23
├── alpine-3.22.pkrvars.hcl # Alpine 3.22
├── alpine-3.21.pkrvars.hcl # Alpine 3.21
├── http/alpine/answers     # Alpine 安装器的自动应答文件
└── ...其他 OS 的 pkrvars.hcl

核心思想:通用构建逻辑和 OS 特定配置分离

  • generic.pkr.hcl 定义了"如何创建一台 VM"——连 Proxmox API、分配 CPU/内存/磁盘、挂载 ISO、执行启动命令、等待 SSH 连接、运行 provisioner。
  • 各个 .pkrvars.hcl 文件定义了"装哪个 ISO、怎么应答安装向导、装完后跑什么命令"。
  • http/alpine/answers 是 Alpine 的 setup-alpine 安装脚本的自动应答文件,实现完全无人值守安装。

前置准备

在开始之前,你需要确认以下条件:

项目要求
Proxmox VE一台运行的 PVE(作者测试于 PVE 9.1.2,通常 7.x/8.x 也能用)
Packer CLI版本 1.14+(后面会装)
能连 PVE你的电脑需要通过 HTTPS 访问 PVE 的管理口(通常是 8006 端口)
Proxmox 用户有 API 权限的用户(家庭实验环境直接用 root@pam + 密码最简单)

💡 认证方式怎么选? 家庭实验环境直接用 root@pam + 登录密码即可,实测完全没问题,也是本文采用的方式。如果是生产环境或多人共用的 PVE,建议创建专用 API Token(PVE Web 界面 → 数据中心 → 权限 → API 令牌),仅授予 VM.Template.CreateVM.Allocate 等必要权限——Token 可以设过期时间,泄露后能单独撤销。


步骤 1:安装 Packer

如果你还没有 Packer,先安装它。

Ubuntu/Debian:

curl -fsSL 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

macOS:

brew tap hashicorp/tap
brew install hashicorp/tap/packer

验证安装:

packer version

步骤 2:下载项目

git clone https://github.com/Pumba98/proxmox-packer-templates.git
cd proxmox-packer-templates

或者也可以直接下载 ZIP:

wget https://github.com/Pumba98/proxmox-packer-templates/archive/refs/heads/master.zip
unzip master.zip
cd proxmox-packer-templates-master

步骤 3:初始化 Packer 插件

项目使用的 Proxmox 插件需要先下载:

packer init config.pkr.hcl

这条命令会读取 config.pkr.hcl 中声明的 hashicorp/proxmox 插件并安装到本地。


步骤 4:配置变量(最关键的一步)

这一步是整个教程的核心,也是最容易出错的地方。你需要创建一个自己的连接配置文件,告诉 Packer 怎么连上你的 PVE。

每个变量都要填对,下面逐个说明它的值到哪里去找。

4.1 先搞清楚每个变量的值在哪里找

变量含义去哪里找
proxmox_hostPVE 的 IP + 端口就是你平时打开 PVE 网页时浏览器地址栏里的地址,例如网页是 https://10.20.20.3:8006,这里就填 "10.20.20.3:8006"(不带 https://
proxmox_userPVE 登录用户PVE 网页登录框里的用户名 + 领域。用 root 密码登录的就是 "root@pam"
proxmox_password登录密码你登录 PVE 网页用的那个密码
nodePVE 节点名⚠️ 最容易填错!打开 PVE 网页,看左侧树状列表里「数据中心」下面那个名字,那才是节点名。它不一定叫 pve——比如我的就叫 local
proxmox_insecure_tls跳过证书校验固定填 true(原因见下面的踩坑记录)
disk_storage_pool存储池名PVE 网页左侧展开你的节点,下面列出的存储名称(如 locallocal-lvm),见 4.2
vmid模板的 VM ID自己定一个没被占用的号,习惯上模板用 9000+;不填会自动分配

⚠️ node 节点名千万别照抄教程写 pve。节点名是装 PVE 时设置的主机名,每台机器都可能不一样。确认方法有两个:

  1. PVE Web 界面左侧「数据中心」下面显示的名字;
  2. SSH 到 PVE 上执行 cat /etc/hostname

填错节点名,构建时会报 unable to find node 之类的错误。

4.2 存储池名怎么确认

在 PVE Web 界面左侧展开你的节点,能看到一个或多个存储(数据库图标),常见有两种情况:

  • 只有一个 local(dir 类型):三个存储池全填 "local",前提是它启用了 Disk imageISO image 内容类型(点开该存储 → 内容 里能看到)。
  • local + local-lvm:ISO 只能放 local,磁盘和 Cloud-Init 盘放 local-lvm
disk_storage_pool       = "local-lvm"
cloud_init_storage_pool = "local-lvm"
iso_storage_pool        = "local"

4.3 创建连接配置文件

在项目目录下创建 credentials.pkrvars.hcl(把注释里说明的值换成你自己查到的):

cat > credentials.pkrvars.hcl << 'EOF'
# PVE 地址与端口(浏览器地址栏里的 IP:端口,不带 https://)
proxmox_host     = "10.20.20.3:8006"

# 登录认证(和登录 PVE 网页用的一样)
proxmox_user     = "root@pam"
proxmox_password = "你的PVE登录密码"

# PVE 节点名(看 Web 界面左侧「数据中心」下面的名字,不一定是 pve!我的是 local)
node             = "local"

# 跳过自签名证书校验(必须加,否则报 TLS 错误,见下文踩坑记录)
proxmox_insecure_tls = true

# 存储池(按 4.2 查到的实际情况填)
disk_storage_pool       = "local"
cloud_init_storage_pool = "local"
iso_storage_pool        = "local"

# 可选:指定模板的 VM ID(不填自动分配)
# vmid = 9000
EOF

💡 这个文件里有你的 PVE 密码,不要提交到 Git 或分享给别人。 更安全的做法是用 API Token(proxmox_token = "packer@pve!token=xxxx..." 替代 proxmox_password),家庭环境用密码就够了。

4.3.1 实际成功的配置实例

下面是我实测构建成功(Alpine / Ubuntu 26.04 / Debian 13 三个模板全部通过)时用的真实配置,环境是:PVE 地址 10.20.20.3:8006、节点名 local、单 local dir 存储池:

# Proxmox 节点地址与端口
proxmox_host     = "10.20.20.3:8006"

# 认证信息(root@pam + 登录密码)
proxmox_user     = "root@pam"
proxmox_password = "********"   # 换成你自己的 PVE 登录密码

# PVE 节点名称(我的 PVE 左侧「数据中心」下显示的是 local,不是 pve)
node             = "local"

# 跳过 TLS 证书校验(注意变量名前缀是 proxmox_,一个字母都不能少)
proxmox_insecure_tls = true

# 存储池设置(我只有一个 local dir 存储池,三个全填 local)
disk_storage_pool       = "local"
cloud_init_storage_pool = "local"
iso_storage_pool        = "local"

照着这个格式,把地址、密码、节点名、存储池换成你在 4.1、4.2 里查到的值即可。

4.4 踩坑记录:TLS 证书报错(几乎必遇)

如果没写 proxmox_insecure_tls = true,构建会秒退并报错:

Build 'build.proxmox-iso.vm' errored after 51 milliseconds:
Post "https://10.20.20.3:8006/api2/json/access/ticket":
tls: failed to verify certificate: x509: certificate is not trusted

原因是 PVE 用的是自签名 HTTPS 证书,Packer 默认会严格校验证书,通不过就直接失败。

关键点:这个项目里跳过证书校验的变量名是 proxmox_insecure_tls,不是别的。 我当时踩了好几个坑才找对:

  • ❌ 写 insecure_skip_tls_verify = true → 无效,这是 Packer 插件底层的参数名,不是本项目暴露的变量名
  • ❌ 写 proxmox_skip_tls_verify = true → 无效,变量名对不上,Packer 静默忽略
  • ❌ 命令行传 -var "proxmox_insecure_skip_tls_verify=true" → 直接报 Undefined -var variable
  • ❌ 去改 generic.pkr.hcl 手动加 insecure_skip_tls_verify → 报 Attribute redefined,因为项目第 25 行本来就有这行
  • ✅ 唯一正确做法:在你的 pkrvars 文件里写 proxmox_insecure_tls = true

为什么是这个名字? 打开项目的 generic.pkr.hcl,能看到这一行:

insecure_skip_tls_verify = var.proxmox_insecure_tls

右边的 var.proxmox_insecure_tls 才是项目作者定义、允许你在 pkrvars 文件里赋值的变量名。以后遇到类似「不知道该用什么变量名」的情况,通用排查方法就是:generic.pkr.hcl 里搜插件参数名,看它等号右边 var. 后面跟的是什么,或者直接翻 variables.pkr.hcl 看全部变量定义。

⚠️ 另一个低级但致命的坑:拼写错误。 我有一次把变量写成了 roxmox_insecure_tls(少了开头的 p),Packer 对认不出来的变量不报错、直接忽略,结果 TLS 错误照旧。改完配置还是报同样的错时,先逐字检查变量名有没有打错。

💡 走代理的用户注意:如果你的终端开着代理(HTTP_PROXY/HTTPS_PROXY),访问内网 PVE 的请求可能被代理接管导致证书错误。先执行 export no_proxy="10.20.20.3,localhost,127.0.0.1"(换成你的 PVE IP)或临时 unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY 再构建。

方法 B(可选):环境变量

不想把密码写进文件?用环境变量,Packer 会自动读取 PKR_VAR_ 开头的变量:

export PKR_VAR_proxmox_host="10.20.20.3:8006"
export PKR_VAR_proxmox_user="root@pam"
export PKR_VAR_proxmox_password="你的密码"
export PKR_VAR_node="local"
export PKR_VAR_proxmox_insecure_tls=true

步骤 5:选择 Alpine 版本

项目根目录下有多个 Alpine 版本的 .pkrvars.hcl 文件:

  • alpine-3.24.pkrvars.hcl最新版(推荐)
  • alpine-3.23.pkrvars.hcl
  • alpine-3.22.pkrvars.hcl
  • alpine-3.21.pkrvars.hcl

Alpine 镜像选的是 alpine-virt 类型——这是最精简的 Alpine 变体,适合虚拟机场景,没有图形界面和无关驱动。

🔍 alpine-3.24.pkrvars.hcl 里引用的是 alpine-virt-3.24.1-x86_64.iso,镜像从 dl-cdn.alpinelinux.org 官方 CDN 下载。


步骤 6:执行构建

一切就绪后,执行构建命令:

packer build -var-file="credentials.pkrvars.hcl" -var-file="alpine-3.24.pkrvars.hcl" .

⚠️ 注意最后的 . 不能丢,它表示在当前目录找 generic.pkr.hcl 作为构建定义。

命令拆解:

  • -var-file="credentials.pkrvars.hcl" —— 你的 Proxmox 连接信息(步骤 4 创建的)
  • -var-file="alpine-3.24.pkrvars.hcl" —— Alpine 3.24 的 ISO、启动命令、安装后脚本
  • . —— Packer 构建源文件目录

构建过程中你会看到类似这样的输出:

proxmox-iso.vm: output will be in this color.

==> proxmox-iso.vm: Starting VM
==> proxmox-iso.vm: Starting HTTP server on 127.0.0.1:8178
==> proxmox-iso.vm: typing boot command...
==> proxmox-iso.vm: Waiting for SSH to become available...
==> proxmox-iso.vm: Connected via SSH!
==> proxmox-iso.vm: Provisioning with shell script: /tmp/packer-shell...
    proxmox-iso.vm: (1/1) Installing cloud-init
    proxmox-iso.vm: (2/1) Installing ...
==> proxmox-iso.vm: Converting VM to template...
Build 'proxmox-iso.vm' finished after 2 minutes 34 seconds.

看到 Converting VM to template... 并成功结束,说明 Alpine VM 模板已经创建好了。

💡 第一构建会比较慢(约 3~5 分钟),因为需要下载 ISO、等待 Alpine 自动安装、再跑安装后脚本。后续构建如果有缓存会快很多。


步骤 7:验证模板

到 Proxmox Web 界面确认:

  1. 登录 PVE Web 界面
  2. 在左侧节点下找到你指定的 VM ID(比如 9000
  3. 你会看到状态显示为 Template(模板图标)
  4. 点进 Hardware 可以看到:1 个 vCPU / 2GB 内存 / SCSI 磁盘

你也可以用 qm 命令验证:

ssh root@pve qm list

输出应该看到 9000 这个 VM,且状态是 running(template 实际上是 stopped,但显示为模板状态)。

🔍 模板里默认装了:qemu-guest-agent(QEMU 通信代理)、cloud-init(云初始化)、sudo,并创建了 wheel 组带免密码 sudo。


步骤 8:从模板创建新 VM

有了模板,创建新 VM 只需要几秒钟:

方式一:PVE Web 界面

  1. 选中模板 VM → 右键 Clone
  2. 填写新 VM 名称和 ID
  3. 选择 Full Clone(完整克隆)或 Linked Clone(链接克隆)
  4. 点击 Clone

方式二:命令行

qm clone 9000 101 --name my-alpine-vm --full

配置 Cloud-Init

克隆出来的 VM 需要配置 Cloud-Init 才能获取 IP 和 SSH 密钥:

  1. 在 PVE Web 上选中新 VM → Cloud-Init 选项卡
  2. 设置:
    • User —— 默认的登录用户名(如 alpine
    • Password —— 登录密码
    • SSH public key —— 贴上你的 SSH 公钥
  3. Regenerate Image
  4. 回到 Hardware,给 VM 加上 Cloud-Init 驱动(如果还没加的话)

⚠️ 注意:Alpine 默认 root 账号是被锁定的(passwd -l root),这是安全设计。你需要通过 Cloud-Init 设置一个普通用户,或者开启 root 密码登录。

首次启动

qm start 101
# 等一会看看 IP
qm guest exec 101 -- ip addr show

后台原理:Packer 到底做了什么?

理解原理有助于你排查问题或自定义。整个过程分阶段:

阶段 1:启动 VM 并挂载 ISO

Packer 通过 Proxmox API 创建一个新 VM,挂载 Alpine ISO,设置从 ISO 启动。

阶段 2:自动按键安装(Boot Command)

Packer 模拟键盘输入,执行一系列按键序列(定义在 alpine-3.24.pkrvars.hclboot_command 中):

  1. root 登录(Alpine Live 环境的默认 root 无密码)
  2. 启用网卡并获取 DHCP IP
  3. 从 Packer 的内置 HTTP 服务器下载 answers 应答文件
  4. 执行 setup-alpine -f answers 自动安装到磁盘
  5. 重启后依次设置 qemu-guest-agentsudo,创建 packer 用户

阶段 3:Provisioner(安装后脚本)

SSH 连入新安装的系统,执行 provisioner 列表中的命令:

provisioner = [
  "apk add --no-cache cloud-init util-linux e2fsprogs-extra",
  "setup-cloud-init",
  "echo \"datasource_list: ['NoCloud']\" > /etc/cloud/cloud.cfg.d/02-datasource.cfg",
  "echo \"PasswordAuthentication yes\" > /etc/ssh/sshd_config.d/01-password-auth.conf",
  "passwd -l root",
  "deluser --remove-home packer"
]

这一步安装了 cloud-init,配置从 NoCloud 数据源读取初始化信息,锁定 root 账号,删除临时创建的 packer 用户。

阶段 4:转换为模板

所有操作完成后,Packer 调用 Proxmox API 将 VM 转换为模板。从此你可以从这个模板快速克隆任意数量的 Alpine VM。


常见问题

Q:构建报 TLS 证书错误,秒退?

见上文 4.4 踩坑记录。一句话:在你的 credentials.pkrvars.hcl 里加 proxmox_insecure_tls = true,注意变量名一个字母都不能错。

Q:构建过程中 PVE 界面显示"未找到 CloudInit 驱动器"?

这是正常现象,不用管。 Packer 此时刚拉起 VM,正挂着原生 ISO 做系统安装,Cloud-Init 盘是构建的最后一步Adding a cloud-init cdrom)才附加的。耐心等终端打印 Converting VM to templateBuild finished,再刷新 PVE 界面就能看到模板带着 Cloud-Init 驱动器了。

Q:构建时卡在 “Waiting for SSH to become available…”

Alpine 的安装和重启需要时间,这是正常的。等待时间取决于你的硬件性能。如果等了超过 10 分钟,可能是:

  • ISO 下载失败(检查网络)
  • boot_command 按键序列和实际安装界面不匹配
  • answers 文件中的网络配置不对

Q:能不能自定义模板参数(CPU、内存、磁盘大小)?

可以。在 credentials.pkrvars.hcl 中覆盖即可:

cpu_cores          = 4
memory             = 4096
disk_size          = "20G"
disk_storage_pool  = "local-lvm"

Q:生成的模板最小磁盘是多大?

默认 5G。如果你想缩小,可以改 disk_size = "2G",但注意 Alpine 安装本身约占 1~2GB。

Q:Alpine 版本更新了怎么办?

项目作者用 Renovate Bot 自动检测 Alpine 新版。你也可以手动改 alpine-3.24.pkrvars.hcl 里的 iso_urliso_checksum 指向新版 ISO。

Q:PVE 和构建机不在同一网络怎么处理?

如果 PVE 在你的数据中心,构建机在本机,PVE 不能直接访问你机器的 HTTP 服务器来获取 answers 文件。项目 README 里提供了两种 SSH 端口转发方案:

  • 如果你的电脑能 SSH 到 PVE:ssh -N -R 127.0.0.1:8000:127.0.0.1:8000 root@pve
  • 同时设置 packer_http_interface = "127.0.0.1"packer_http_port = 8000

Q:想再装其他系统的模板(Ubuntu、Debian……)?

连接配置一个字都不用改,只换第二个 -var-file 即可。比如实测过的 Ubuntu 26.04 和 Debian 13:

packer build -var-file="credentials.pkrvars.hcl" -var-file="ubuntu-26.04.pkrvars.hcl" .
packer build -var-file="credentials.pkrvars.hcl" -var-file="debian-13.pkrvars.hcl" .

项目根目录 ls *.pkrvars.hcl 就能看到全部可选系统。注意给每个模板分配不同的 vmid(或者留空自动分配),Ubuntu/Debian 的构建时间比 Alpine 长不少(要下载更大的 ISO、跑完整安装器),耐心等即可。


总结

到这里你已经学会了:

  • ✅ 使用 Packer 在 Proxmox 上自动化构建 Alpine Linux VM 模板
  • ✅ 每个连接变量(地址、用户、节点名、存储池)到 PVE 界面哪里去查
  • ✅ 解决几乎必遇的自签名证书 TLS 报错(proxmox_insecure_tls = true
  • ✅ 理解 Packer 的构建流程:ISO → 自动安装 → 配置 → 模板
  • ✅ 从模板快速克隆并配置 Cloud-Init
  • ✅ 换一个 .pkrvars.hcl 文件就能构建 Ubuntu、Debian 等其他系统模板

这个流程最大的价值在于可重复版本化。以后不管是要部署多台 VM,还是需要统一的镜像基线,用 Packer 构建模板都比手动安装可靠得多。

项目地址:github.com/Pumba98/proxmox-packer-templates


实战验证

本教程的步骤在两套真实环境里都完整跑通过:

实测一:Linux 构建机 + Alpine 3.24

环境:

  • Proxmox VE 9.1.7,单节点
  • Packer 1.14.0(Proxmox Plugin v1.2.3)
  • Alpine alpine-virt-3.24.1-x86_64.iso
  • VM 规格:1 vCPU / 1GB 内存 / 2GB 磁盘

构建耗时:2 分钟

构建日志关键片段:

build.proxmox-iso.vm: Creating VM
build.proxmox-iso.vm: Starting VM
build.proxmox-iso.vm: Starting HTTP server on port 8910
build.proxmox-iso.vm: Waiting 10s for boot
build.proxmox-iso.vm: Typing the boot command
build.proxmox-iso.vm: Waiting for SSH to become available...
build.proxmox-iso.vm: Connected to SSH!
build.proxmox-iso.vm: Provisioning with shell script...
build.proxmox-iso.vm: (153/153) Installing findmnt
build.proxmox-iso.vm: OK: 144.1 MiB in 227 packages
build.proxmox-iso.vm: Stopping VM
build.proxmox-iso.vm: Converting VM to template
build.proxmox-iso.vm: Adding a cloud-init cdrom in storage pool local
Build 'build.proxmox-iso.vm' finished after 1 minute 57 seconds.
A template was created: 922

模板内容:

  • Alpine Linux 3.24(virt 精简版)
  • QEMU Guest Agent 已安装并启用
  • Cloud-Init 已安装并配置(NoCloud 数据源)
  • sudo 已安装,wheel 组免密码 sudo
  • root 账号已锁定(安全)
  • SSH 密码认证已开启
  • Cloud-Init CDROM 已附加

实测二:macOS 构建机 + Ubuntu 26.04 / Debian 13

环境:

  • 构建机:MacBook Air(Apple Silicon,macOS + Homebrew 安装的 Packer,Proxmox Plugin v1.2.4)
  • Proxmox VE:10.20.20.3:8006,节点名 local(就是这台机器让我发现节点名不一定叫 pve
  • 认证:root@pam + 密码
  • 存储:单 local dir 存储池,三个 storage_pool 全填 "local"

这一轮实测踩过的所有坑(正是本文 4.4 节踩坑记录的来源):

  1. 首次构建报 tls: failed to verify certificate: x509 certificate is not trusted —— 自签名证书问题
  2. 依次试了 insecure_skip_tls_verifyproxmox_skip_tls_verify、命令行 -var 传参、手改 generic.pkr.hcl,全部失败(分别是变量名不对、Undefined -var variableAttribute redefined
  3. 最终在 generic.pkr.hcl 里查到 insecure_skip_tls_verify = var.proxmox_insecure_tls,确认正确变量名是 proxmox_insecure_tls
  4. 改对变量名后还报错——检查发现手滑打成了 roxmox_insecure_tls,少个 p,Packer 静默忽略不报错
  5. 修正拼写后一次构建成功;构建中途 PVE 显示"未找到 CloudInit 驱动器"是正常过程,收尾阶段自动附加
  6. Ubuntu 26.04 模板构建成功后,复用同一个 credentials.pkrvars.hcl、只换 var-file 又构建了 Debian 13 模板,零改动直接成功

两轮实测覆盖了 Linux 和 macOS 构建机、密码和 Token 两种认证、Alpine/Ubuntu/Debian 三种系统——按本文步骤操作即可复现。